8686use PHPStan \Type \TypeWithClassName ;
8787use PHPStan \Type \UnionType ;
8888use PHPStan \Type \VerbosityLevel ;
89+ use PHPStan \Type \VoidType ;
8990use function array_key_exists ;
9091
9192class MutatingScope implements Scope
@@ -115,6 +116,8 @@ class MutatingScope implements Scope
115116
116117 private Parser $ parser ;
117118
119+ private NodeScopeResolver $ nodeScopeResolver ;
120+
118121 private \PHPStan \Analyser \ScopeContext $ context ;
119122
120123 /** @var \PHPStan\Type\Type[] */
@@ -172,6 +175,7 @@ class MutatingScope implements Scope
172175 * @param \PHPStan\Analyser\TypeSpecifier $typeSpecifier
173176 * @param \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder
174177 * @param Parser $parser
178+ * @param NodeScopeResolver $nodeScopeResolver
175179 * @param \PHPStan\Analyser\ScopeContext $context
176180 * @param bool $declareStrictTypes
177181 * @param array<string, Type> $constantTypes
@@ -200,6 +204,7 @@ public function __construct(
200204 TypeSpecifier $ typeSpecifier ,
201205 PropertyReflectionFinder $ propertyReflectionFinder ,
202206 Parser $ parser ,
207+ NodeScopeResolver $ nodeScopeResolver ,
203208 ScopeContext $ context ,
204209 bool $ declareStrictTypes = false ,
205210 array $ constantTypes = [],
@@ -232,6 +237,7 @@ public function __construct(
232237 $ this ->typeSpecifier = $ typeSpecifier ;
233238 $ this ->propertyReflectionFinder = $ propertyReflectionFinder ;
234239 $ this ->parser = $ parser ;
240+ $ this ->nodeScopeResolver = $ nodeScopeResolver ;
235241 $ this ->context = $ context ;
236242 $ this ->declareStrictTypes = $ declareStrictTypes ;
237243 $ this ->constantTypes = $ constantTypes ;
@@ -1317,7 +1323,37 @@ private function resolveType(Expr $node): Type
13171323 $ returnType = TypehintHelper::decideType ($ this ->getFunctionType ($ node ->returnType , false , false ), $ returnType );
13181324 }
13191325 } else {
1320- $ returnType = $ this ->getFunctionType ($ node ->returnType , $ node ->returnType === null , false );
1326+ $ closureScope = $ this ->enterAnonymousFunctionWithoutReflection ($ node );
1327+ $ closureReturnStatements = [];
1328+ $ this ->nodeScopeResolver ->processStmtNodes ($ node , $ node ->stmts , $ closureScope , static function (Node $ node , Scope $ scope ) use (&$ closureReturnStatements ): void {
1329+ if (!$ node instanceof Node \Stmt \Return_) {
1330+ return ;
1331+ }
1332+
1333+ $ closureReturnStatements [] = [$ node , $ scope ];
1334+ });
1335+
1336+ $ returnTypes = [];
1337+ $ hasNull = false ;
1338+ foreach ($ closureReturnStatements as [$ returnNode , $ returnScope ]) {
1339+ if ($ returnNode ->expr === null ) {
1340+ $ hasNull = true ;
1341+ continue ;
1342+ }
1343+
1344+ $ returnTypes [] = $ returnScope ->getType ($ returnNode ->expr );
1345+ }
1346+
1347+ if (count ($ returnTypes ) === 0 ) {
1348+ $ returnType = new VoidType ();
1349+ } else {
1350+ if ($ hasNull ) {
1351+ $ returnTypes [] = new NullType ();
1352+ }
1353+ $ returnType = TypeCombinator::union (...$ returnTypes );
1354+ }
1355+
1356+ $ returnType = TypehintHelper::decideType ($ this ->getFunctionType ($ node ->returnType , false , false ), $ returnType );
13211357 }
13221358
13231359 return new ClosureType (
@@ -2079,6 +2115,7 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope
20792115 $ this ->typeSpecifier ,
20802116 $ this ->propertyReflectionFinder ,
20812117 $ this ->parser ,
2118+ $ this ->nodeScopeResolver ,
20822119 $ this ->context ,
20832120 $ this ->declareStrictTypes ,
20842121 $ this ->constantTypes ,
@@ -2710,6 +2747,43 @@ public function enterAnonymousFunction(
27102747 Expr \Closure $ closure ,
27112748 ?array $ callableParameters = null
27122749 ): self
2750+ {
2751+ $ anonymousFunctionReflection = $ this ->getType ($ closure );
2752+ if (!$ anonymousFunctionReflection instanceof ClosureType) {
2753+ throw new \PHPStan \ShouldNotHappenException ();
2754+ }
2755+
2756+ $ scope = $ this ->enterAnonymousFunctionWithoutReflection ($ closure , $ callableParameters );
2757+
2758+ return $ this ->scopeFactory ->create (
2759+ $ scope ->context ,
2760+ $ scope ->isDeclareStrictTypes (),
2761+ $ scope ->constantTypes ,
2762+ $ scope ->getFunction (),
2763+ $ scope ->getNamespace (),
2764+ $ scope ->variableTypes ,
2765+ $ scope ->moreSpecificTypes ,
2766+ [],
2767+ $ scope ->inClosureBindScopeClass ,
2768+ $ anonymousFunctionReflection ,
2769+ true ,
2770+ [],
2771+ $ scope ->nativeExpressionTypes ,
2772+ [],
2773+ false ,
2774+ $ this
2775+ );
2776+ }
2777+
2778+ /**
2779+ * @param \PhpParser\Node\Expr\Closure $closure
2780+ * @param \PHPStan\Reflection\ParameterReflection[]|null $callableParameters
2781+ * @return self
2782+ */
2783+ private function enterAnonymousFunctionWithoutReflection (
2784+ Expr \Closure $ closure ,
2785+ ?array $ callableParameters = null
2786+ ): self
27132787 {
27142788 $ variableTypes = [];
27152789 foreach ($ closure ->params as $ i => $ parameter ) {
@@ -2773,11 +2847,6 @@ public function enterAnonymousFunction(
27732847 $ variableTypes ['this ' ] = VariableTypeHolder::createYes ($ this ->getVariableType ('this ' ));
27742848 }
27752849
2776- $ anonymousFunctionReflection = $ this ->getType ($ closure );
2777- if (!$ anonymousFunctionReflection instanceof ClosureType) {
2778- throw new \PHPStan \ShouldNotHappenException ();
2779- }
2780-
27812850 return $ this ->scopeFactory ->create (
27822851 $ this ->context ,
27832852 $ this ->isDeclareStrictTypes (),
@@ -2788,7 +2857,7 @@ public function enterAnonymousFunction(
27882857 $ moreSpecificTypes ,
27892858 [],
27902859 $ this ->inClosureBindScopeClass ,
2791- $ anonymousFunctionReflection ,
2860+ null ,
27922861 true ,
27932862 [],
27942863 $ nativeTypes ,
0 commit comments