114
114
use PHPStan \Reflection \MethodReflection ;
115
115
use PHPStan \Reflection \Native \NativeMethodReflection ;
116
116
use PHPStan \Reflection \Native \NativeParameterReflection ;
117
+ use PHPStan \Reflection \ParameterReflectionWithPhpDocs ;
117
118
use PHPStan \Reflection \ParametersAcceptor ;
118
119
use PHPStan \Reflection \ParametersAcceptorSelector ;
119
120
use PHPStan \Reflection \Php \PhpMethodReflection ;
@@ -407,7 +408,7 @@ private function processStmtNode(
407
408
$ hasYield = false ;
408
409
$ throwPoints = [];
409
410
$ 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 );
411
412
412
413
foreach ($ stmt ->params as $ param ) {
413
414
$ this ->processParamNode ($ param , $ scope , $ nodeCallback );
@@ -431,6 +432,7 @@ private function processStmtNode(
431
432
$ acceptsNamedArguments ,
432
433
$ asserts ,
433
434
$ phpDocComment ,
435
+ $ phpDocParameterOutTypes ,
434
436
);
435
437
$ functionReflection = $ functionScope ->getFunction ();
436
438
if (!$ functionReflection instanceof FunctionReflection) {
@@ -470,7 +472,7 @@ private function processStmtNode(
470
472
$ hasYield = false ;
471
473
$ throwPoints = [];
472
474
$ 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 );
474
476
475
477
foreach ($ stmt ->params as $ param ) {
476
478
$ this ->processParamNode ($ param , $ scope , $ nodeCallback );
@@ -495,6 +497,7 @@ private function processStmtNode(
495
497
$ asserts ,
496
498
$ selfOutType ,
497
499
$ phpDocComment ,
500
+ $ phpDocParameterOutTypes ,
498
501
);
499
502
500
503
if ($ stmt ->name ->toLowerString () === '__construct ' ) {
@@ -1806,6 +1809,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
1806
1809
$ functionReflection ->getVariants (),
1807
1810
);
1808
1811
}
1812
+
1809
1813
if ($ parametersAcceptor !== null ) {
1810
1814
$ expr = ArgumentsNormalizer::reorderFuncArguments ($ parametersAcceptor , $ expr ) ?? $ expr ;
1811
1815
}
@@ -2043,11 +2047,13 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
2043
2047
}
2044
2048
}
2045
2049
}
2050
+
2046
2051
if ($ parametersAcceptor !== null ) {
2047
2052
$ expr = ArgumentsNormalizer::reorderMethodArguments ($ parametersAcceptor , $ expr ) ?? $ expr ;
2048
2053
}
2049
2054
$ result = $ this ->processArgs ($ methodReflection , $ parametersAcceptor , $ expr ->getArgs (), $ scope , $ nodeCallback , $ context );
2050
2055
$ scope = $ result ->getScope ();
2056
+
2051
2057
if ($ methodReflection !== null ) {
2052
2058
$ hasSideEffects = $ methodReflection ->hasSideEffects ();
2053
2059
if ($ hasSideEffects ->yes () || $ methodReflection ->getName () === '__construct ' ) {
@@ -2174,12 +2180,14 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
2174
2180
$ throwPoints [] = ThrowPoint::createImplicit ($ scope , $ expr );
2175
2181
}
2176
2182
}
2183
+
2177
2184
if ($ parametersAcceptor !== null ) {
2178
2185
$ expr = ArgumentsNormalizer::reorderStaticCallArguments ($ parametersAcceptor , $ expr ) ?? $ expr ;
2179
2186
}
2180
2187
$ result = $ this ->processArgs ($ methodReflection , $ parametersAcceptor , $ expr ->getArgs (), $ scope , $ nodeCallback , $ context , $ closureBindScope ?? null );
2181
2188
$ scope = $ result ->getScope ();
2182
2189
$ scopeFunction = $ scope ->getFunction ();
2190
+
2183
2191
if (
2184
2192
$ methodReflection !== null
2185
2193
&& !$ methodReflection ->isStatic ()
@@ -2529,6 +2537,7 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
2529
2537
$ throwPoints [] = ThrowPoint::createImplicit ($ scope , $ expr );
2530
2538
}
2531
2539
}
2540
+
2532
2541
if ($ parametersAcceptor !== null ) {
2533
2542
$ expr = ArgumentsNormalizer::reorderNewArguments ($ parametersAcceptor , $ expr ) ?? $ expr ;
2534
2543
}
@@ -3272,8 +3281,21 @@ private function processArgs(
3272
3281
?MutatingScope $ closureBindScope = null ,
3273
3282
): ExpressionResult
3274
3283
{
3284
+ $ paramOutTypes = [];
3275
3285
if ($ parametersAcceptor !== null ) {
3276
3286
$ 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
+ }
3277
3299
}
3278
3300
3279
3301
if ($ calleeReflection !== null ) {
@@ -3286,20 +3308,29 @@ private function processArgs(
3286
3308
$ originalArg = $ arg ->getAttribute (ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE ) ?? $ arg ;
3287
3309
$ nodeCallback ($ originalArg , $ scope );
3288
3310
if (isset ($ parameters ) && $ parametersAcceptor !== null ) {
3311
+ $ byRefType = new MixedType ();
3289
3312
$ assignByReference = false ;
3290
3313
if (isset ($ parameters [$ i ])) {
3291
3314
$ assignByReference = $ parameters [$ i ]->passedByReference ()->createsNewVariable ();
3292
3315
$ parameterType = $ parameters [$ i ]->getType ();
3316
+
3317
+ if (isset ($ paramOutTypes [$ parameters [$ i ]->getName ()])) {
3318
+ $ byRefType = $ paramOutTypes [$ parameters [$ i ]->getName ()];
3319
+ }
3293
3320
} elseif (count ($ parameters ) > 0 && $ parametersAcceptor ->isVariadic ()) {
3294
3321
$ lastParameter = $ parameters [count ($ parameters ) - 1 ];
3295
3322
$ assignByReference = $ lastParameter ->passedByReference ()->createsNewVariable ();
3296
3323
$ parameterType = $ lastParameter ->getType ();
3324
+
3325
+ if (isset ($ paramOutTypes [$ lastParameter ->getName ()])) {
3326
+ $ byRefType = $ paramOutTypes [$ lastParameter ->getName ()];
3327
+ }
3297
3328
}
3298
3329
3299
3330
if ($ assignByReference ) {
3300
3331
$ argValue = $ arg ->value ;
3301
3332
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 ());
3303
3334
}
3304
3335
}
3305
3336
}
@@ -4039,7 +4070,7 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection
4039
4070
}
4040
4071
4041
4072
/**
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> }
4043
4074
*/
4044
4075
public function getPhpDocs (Scope $ scope , Node \FunctionLike |Node \Stmt \Property $ node ): array
4045
4076
{
@@ -4065,6 +4096,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
4065
4096
$ trait = $ scope ->isInTrait () ? $ scope ->getTraitReflection ()->getName () : null ;
4066
4097
$ resolvedPhpDoc = null ;
4067
4098
$ functionName = null ;
4099
+ $ phpDocParameterOutTypes = [];
4068
4100
4069
4101
if ($ node instanceof Node \Stmt \ClassMethod) {
4070
4102
if (!$ scope ->isInClass ()) {
@@ -4149,6 +4181,9 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
4149
4181
}
4150
4182
$ phpDocParameterTypes [$ paramName ] = $ paramType ;
4151
4183
}
4184
+ foreach ($ resolvedPhpDoc ->getParamOutTags () as $ paramName => $ paramOutTag ) {
4185
+ $ phpDocParameterOutTypes [$ paramName ] = $ paramOutTag ->getType ();
4186
+ }
4152
4187
if ($ node instanceof Node \FunctionLike) {
4153
4188
$ nativeReturnType = $ scope ->getFunctionType ($ node ->getReturnType (), false , false );
4154
4189
$ phpDocReturnType = $ this ->getPhpDocReturnType ($ resolvedPhpDoc , $ nativeReturnType );
@@ -4168,7 +4203,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
4168
4203
$ selfOutType = $ resolvedPhpDoc ->getSelfOutTag () !== null ? $ resolvedPhpDoc ->getSelfOutTag ()->getType () : null ;
4169
4204
}
4170
4205
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 ];
4172
4207
}
4173
4208
4174
4209
private function transformStaticType (ClassReflection $ declaringClass , Type $ type ): Type
0 commit comments