114114use PHPStan \Reflection \MethodReflection ;
115115use PHPStan \Reflection \Native \NativeMethodReflection ;
116116use PHPStan \Reflection \Native \NativeParameterReflection ;
117+ use PHPStan \Reflection \ParameterReflectionWithPhpDocs ;
117118use PHPStan \Reflection \ParametersAcceptor ;
118119use PHPStan \Reflection \ParametersAcceptorSelector ;
119120use PHPStan \Reflection \Php \PhpMethodReflection ;
@@ -407,7 +408,7 @@ private function processStmtNode(
407408 $ hasYield = false ;
408409 $ throwPoints = [];
409410 $ this ->processAttributeGroups ($ stmt ->attrGroups , $ scope , $ nodeCallback );
410- [$ templateTypeMap , $ phpDocParameterTypes , $ phpDocReturnType , $ phpDocThrowType , $ deprecatedDescription , $ isDeprecated , $ isInternal , $ isFinal , $ isPure , $ acceptsNamedArguments , , $ phpDocComment , $ asserts ] = $ this ->getPhpDocs ($ scope , $ stmt );
411+ [$ templateTypeMap , $ phpDocParameterTypes , $ phpDocReturnType , $ phpDocThrowType , $ deprecatedDescription , $ isDeprecated , $ isInternal , $ isFinal , $ isPure , $ acceptsNamedArguments , , $ phpDocComment , $ asserts,, $ phpDocParameterOutTypes ] = $ this ->getPhpDocs ($ scope , $ stmt );
411412
412413 foreach ($ stmt ->params as $ param ) {
413414 $ this ->processParamNode ($ param , $ scope , $ nodeCallback );
@@ -431,6 +432,7 @@ private function processStmtNode(
431432 $ acceptsNamedArguments ,
432433 $ asserts ,
433434 $ phpDocComment ,
435+ $ phpDocParameterOutTypes ,
434436 );
435437 $ functionReflection = $ functionScope ->getFunction ();
436438 if (!$ functionReflection instanceof FunctionReflection) {
@@ -470,7 +472,7 @@ private function processStmtNode(
470472 $ hasYield = false ;
471473 $ throwPoints = [];
472474 $ this ->processAttributeGroups ($ stmt ->attrGroups , $ scope , $ nodeCallback );
473- [$ templateTypeMap , $ phpDocParameterTypes , $ phpDocReturnType , $ phpDocThrowType , $ deprecatedDescription , $ isDeprecated , $ isInternal , $ isFinal , $ isPure , $ acceptsNamedArguments , , $ phpDocComment , $ asserts , $ selfOutType ] = $ this ->getPhpDocs ($ scope , $ stmt );
475+ [$ templateTypeMap , $ phpDocParameterTypes , $ phpDocReturnType , $ phpDocThrowType , $ deprecatedDescription , $ isDeprecated , $ isInternal , $ isFinal , $ isPure , $ acceptsNamedArguments , , $ phpDocComment , $ asserts , $ selfOutType, $ phpDocParameterOutTypes ] = $ this ->getPhpDocs ($ scope , $ stmt );
474476
475477 foreach ($ stmt ->params as $ param ) {
476478 $ this ->processParamNode ($ param , $ scope , $ nodeCallback );
@@ -495,6 +497,7 @@ private function processStmtNode(
495497 $ asserts ,
496498 $ selfOutType ,
497499 $ phpDocComment ,
500+ $ phpDocParameterOutTypes ,
498501 );
499502
500503 if ($ stmt ->name ->toLowerString () === '__construct ' ) {
@@ -1806,6 +1809,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
18061809 $ functionReflection ->getVariants (),
18071810 );
18081811 }
1812+
18091813 if ($ parametersAcceptor !== null ) {
18101814 $ expr = ArgumentsNormalizer::reorderFuncArguments ($ parametersAcceptor , $ expr ) ?? $ expr ;
18111815 }
@@ -2043,11 +2047,13 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
20432047 }
20442048 }
20452049 }
2050+
20462051 if ($ parametersAcceptor !== null ) {
20472052 $ expr = ArgumentsNormalizer::reorderMethodArguments ($ parametersAcceptor , $ expr ) ?? $ expr ;
20482053 }
20492054 $ result = $ this ->processArgs ($ methodReflection , $ parametersAcceptor , $ expr ->getArgs (), $ scope , $ nodeCallback , $ context );
20502055 $ scope = $ result ->getScope ();
2056+
20512057 if ($ methodReflection !== null ) {
20522058 $ hasSideEffects = $ methodReflection ->hasSideEffects ();
20532059 if ($ hasSideEffects ->yes () || $ methodReflection ->getName () === '__construct ' ) {
@@ -2174,12 +2180,14 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
21742180 $ throwPoints [] = ThrowPoint::createImplicit ($ scope , $ expr );
21752181 }
21762182 }
2183+
21772184 if ($ parametersAcceptor !== null ) {
21782185 $ expr = ArgumentsNormalizer::reorderStaticCallArguments ($ parametersAcceptor , $ expr ) ?? $ expr ;
21792186 }
21802187 $ result = $ this ->processArgs ($ methodReflection , $ parametersAcceptor , $ expr ->getArgs (), $ scope , $ nodeCallback , $ context , $ closureBindScope ?? null );
21812188 $ scope = $ result ->getScope ();
21822189 $ scopeFunction = $ scope ->getFunction ();
2190+
21832191 if (
21842192 $ methodReflection !== null
21852193 && !$ methodReflection ->isStatic ()
@@ -2529,6 +2537,7 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
25292537 $ throwPoints [] = ThrowPoint::createImplicit ($ scope , $ expr );
25302538 }
25312539 }
2540+
25322541 if ($ parametersAcceptor !== null ) {
25332542 $ expr = ArgumentsNormalizer::reorderNewArguments ($ parametersAcceptor , $ expr ) ?? $ expr ;
25342543 }
@@ -3272,8 +3281,21 @@ private function processArgs(
32723281 ?MutatingScope $ closureBindScope = null ,
32733282 ): ExpressionResult
32743283 {
3284+ $ paramOutTypes = [];
32753285 if ($ parametersAcceptor !== null ) {
32763286 $ parameters = $ parametersAcceptor ->getParameters ();
3287+
3288+ foreach ($ parameters as $ parameter ) {
3289+ if (!$ parameter instanceof ParameterReflectionWithPhpDocs) {
3290+ continue ;
3291+ }
3292+
3293+ if ($ parameter ->getOutType () === null ) {
3294+ continue ;
3295+ }
3296+
3297+ $ paramOutTypes [$ parameter ->getName ()] = TemplateTypeHelper::resolveTemplateTypes ($ parameter ->getOutType (), $ parametersAcceptor ->getResolvedTemplateTypeMap ());
3298+ }
32773299 }
32783300
32793301 if ($ calleeReflection !== null ) {
@@ -3286,20 +3308,29 @@ private function processArgs(
32863308 $ originalArg = $ arg ->getAttribute (ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE ) ?? $ arg ;
32873309 $ nodeCallback ($ originalArg , $ scope );
32883310 if (isset ($ parameters ) && $ parametersAcceptor !== null ) {
3311+ $ byRefType = new MixedType ();
32893312 $ assignByReference = false ;
32903313 if (isset ($ parameters [$ i ])) {
32913314 $ assignByReference = $ parameters [$ i ]->passedByReference ()->createsNewVariable ();
32923315 $ parameterType = $ parameters [$ i ]->getType ();
3316+
3317+ if (isset ($ paramOutTypes [$ parameters [$ i ]->getName ()])) {
3318+ $ byRefType = $ paramOutTypes [$ parameters [$ i ]->getName ()];
3319+ }
32933320 } elseif (count ($ parameters ) > 0 && $ parametersAcceptor ->isVariadic ()) {
32943321 $ lastParameter = $ parameters [count ($ parameters ) - 1 ];
32953322 $ assignByReference = $ lastParameter ->passedByReference ()->createsNewVariable ();
32963323 $ parameterType = $ lastParameter ->getType ();
3324+
3325+ if (isset ($ paramOutTypes [$ lastParameter ->getName ()])) {
3326+ $ byRefType = $ paramOutTypes [$ lastParameter ->getName ()];
3327+ }
32973328 }
32983329
32993330 if ($ assignByReference ) {
33003331 $ argValue = $ arg ->value ;
33013332 if ($ argValue instanceof Variable && is_string ($ argValue ->name )) {
3302- $ scope = $ scope ->assignVariable ($ argValue ->name , new MixedType () , new MixedType ());
3333+ $ scope = $ scope ->assignVariable ($ argValue ->name , $ byRefType , new MixedType ());
33033334 }
33043335 }
33053336 }
@@ -4039,7 +4070,7 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection
40394070 }
40404071
40414072 /**
4042- * @return array{TemplateTypeMap, Type[] , ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type}
4073+ * @return array{TemplateTypeMap, array<string, Type> , ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type, array<string, Type> }
40434074 */
40444075 public function getPhpDocs (Scope $ scope , Node \FunctionLike |Node \Stmt \Property $ node ): array
40454076 {
@@ -4065,6 +4096,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
40654096 $ trait = $ scope ->isInTrait () ? $ scope ->getTraitReflection ()->getName () : null ;
40664097 $ resolvedPhpDoc = null ;
40674098 $ functionName = null ;
4099+ $ phpDocParameterOutTypes = [];
40684100
40694101 if ($ node instanceof Node \Stmt \ClassMethod) {
40704102 if (!$ scope ->isInClass ()) {
@@ -4149,6 +4181,9 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
41494181 }
41504182 $ phpDocParameterTypes [$ paramName ] = $ paramType ;
41514183 }
4184+ foreach ($ resolvedPhpDoc ->getParamOutTags () as $ paramName => $ paramOutTag ) {
4185+ $ phpDocParameterOutTypes [$ paramName ] = $ paramOutTag ->getType ();
4186+ }
41524187 if ($ node instanceof Node \FunctionLike) {
41534188 $ nativeReturnType = $ scope ->getFunctionType ($ node ->getReturnType (), false , false );
41544189 $ phpDocReturnType = $ this ->getPhpDocReturnType ($ resolvedPhpDoc , $ nativeReturnType );
@@ -4168,7 +4203,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
41684203 $ selfOutType = $ resolvedPhpDoc ->getSelfOutTag () !== null ? $ resolvedPhpDoc ->getSelfOutTag ()->getType () : null ;
41694204 }
41704205
4171- return [$ templateTypeMap , $ phpDocParameterTypes , $ phpDocReturnType , $ phpDocThrowType , $ deprecatedDescription , $ isDeprecated , $ isInternal , $ isFinal , $ isPure , $ acceptsNamedArguments , $ isReadOnly , $ docComment , $ asserts , $ selfOutType ];
4206+ return [$ templateTypeMap , $ phpDocParameterTypes , $ phpDocReturnType , $ phpDocThrowType , $ deprecatedDescription , $ isDeprecated , $ isInternal , $ isFinal , $ isPure , $ acceptsNamedArguments , $ isReadOnly , $ docComment , $ asserts , $ selfOutType, $ phpDocParameterOutTypes ];
41724207 }
41734208
41744209 private function transformStaticType (ClassReflection $ declaringClass , Type $ type ): Type
0 commit comments