Skip to content

Commit 5f064dd

Browse files
authored
More precise types in immediately invoked callables
1 parent 5950910 commit 5f064dd

File tree

3 files changed

+61
-11
lines changed

3 files changed

+61
-11
lines changed

src/Analyser/MutatingScope.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
use PHPStan\Node\Printer\ExprPrinter;
4747
use PHPStan\Node\PropertyAssignNode;
4848
use PHPStan\Parser\ArrayMapArgVisitor;
49-
use PHPStan\Parser\ImmediatelyInvokedClosureVisitor;
5049
use PHPStan\Parser\NewAssignedToPropertyVisitor;
5150
use PHPStan\Parser\Parser;
5251
use PHPStan\Php\PhpVersion;
@@ -4795,7 +4794,6 @@ private function processFinallyScopeVariableTypeHolders(
47954794
* @param Expr\ClosureUse[] $byRefUses
47964795
*/
47974796
public function processClosureScope(
4798-
Expr\Closure $expr,
47994797
self $closureScope,
48004798
?self $prevScope,
48014799
array $byRefUses,
@@ -4828,9 +4826,7 @@ public function processClosureScope(
48284826
$prevVariableType = $prevScope->getVariableType($variableName);
48294827
if (!$variableType->equals($prevVariableType)) {
48304828
$variableType = TypeCombinator::union($variableType, $prevVariableType);
4831-
if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) !== true) {
4832-
$variableType = self::generalizeType($variableType, $prevVariableType, 0);
4833-
}
4829+
$variableType = self::generalizeType($variableType, $prevVariableType, 0);
48344830
}
48354831
}
48364832

src/Analyser/NodeScopeResolver.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
use PHPStan\Node\VarTagChangedExpressionTypeNode;
120120
use PHPStan\Parser\ArrowFunctionArgVisitor;
121121
use PHPStan\Parser\ClosureArgVisitor;
122+
use PHPStan\Parser\ImmediatelyInvokedClosureVisitor;
122123
use PHPStan\Parser\Parser;
123124
use PHPStan\Php\PhpVersion;
124125
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
@@ -4232,7 +4233,7 @@ private function processClosureNode(
42324233
}
42334234

42344235
$closureScope = $scope->enterAnonymousFunction($expr, $callableParameters);
4235-
$closureScope = $closureScope->processClosureScope($expr, $scope, null, $byRefUses);
4236+
$closureScope = $closureScope->processClosureScope($scope, null, $byRefUses);
42364237
$closureType = $closureScope->getAnonymousFunctionReflection();
42374238
if (!$closureType instanceof ClosureType) {
42384239
throw new ShouldNotHappenException();
@@ -4277,6 +4278,7 @@ private function processClosureNode(
42774278

42784279
$gatheredReturnStatements[] = new ReturnStatement($scope, $node);
42794280
};
4281+
42804282
if (count($byRefUses) === 0) {
42814283
$statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel());
42824284
$nodeCallback(new ClosureReturnStatementsNode(
@@ -4292,6 +4294,7 @@ private function processClosureNode(
42924294
}
42934295

42944296
$count = 0;
4297+
$closureResultScope = null;
42954298
do {
42964299
$prevScope = $closureScope;
42974300

@@ -4301,8 +4304,15 @@ private function processClosureNode(
43014304
foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) {
43024305
$intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope());
43034306
}
4307+
4308+
if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) === true) {
4309+
$closureResultScope = $intermediaryClosureScope;
4310+
break;
4311+
}
4312+
43044313
$closureScope = $scope->enterAnonymousFunction($expr, $callableParameters);
4305-
$closureScope = $closureScope->processClosureScope($expr, $intermediaryClosureScope, $prevScope, $byRefUses);
4314+
$closureScope = $closureScope->processClosureScope($intermediaryClosureScope, $prevScope, $byRefUses);
4315+
43064316
if ($closureScope->equals($prevScope)) {
43074317
break;
43084318
}
@@ -4312,6 +4322,10 @@ private function processClosureNode(
43124322
$count++;
43134323
} while ($count < self::LOOP_SCOPE_ITERATIONS);
43144324

4325+
if ($closureResultScope === null) {
4326+
$closureResultScope = $closureScope;
4327+
}
4328+
43154329
$statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel());
43164330
$nodeCallback(new ClosureReturnStatementsNode(
43174331
$expr,
@@ -4322,7 +4336,7 @@ private function processClosureNode(
43224336
array_merge($statementResult->getImpurePoints(), $closureImpurePoints),
43234337
), $closureScope);
43244338

4325-
return new ProcessClosureResult($scope->processClosureScope($expr, $closureScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions);
4339+
return new ProcessClosureResult($scope->processClosureScope($closureResultScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions);
43264340
}
43274341

43284342
/**

tests/PHPStan/Analyser/nsrt/bug-11561.php

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ function main(mixed $c): void{
1212
assertType('array{date: DateTime, id: 1}', $c);
1313

1414
$x = (function() use (&$c) {
15-
assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c);
15+
assertType("array{date: DateTime, id: 1}", $c);
1616
$c['name'] = 'ruud';
1717
assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
1818
return 'x';
1919
})();
2020

21-
assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c);
21+
assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
2222
}
2323

2424

@@ -30,11 +30,51 @@ function main2(mixed $c): void{
3030
assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);
3131

3232
$x = (function() use (&$c) {
33-
assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c);
33+
assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);
3434
$c['name'] = 'ruud';
3535
assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
3636
return 'x';
3737
})();
3838

39+
assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
40+
}
41+
42+
/** @param array{date: DateTime} $c */
43+
function main3(mixed $c): void{
44+
assertType('array{date: DateTime}', $c);
45+
$c['id']=1;
46+
$c['name'] = 'staabm';
47+
assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);
48+
49+
$x = (function() use (&$c) {
50+
assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);
51+
if (rand(0,1)) {
52+
$c['name'] = 'ruud';
53+
}
54+
assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c);
55+
return 'x';
56+
})();
57+
58+
assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c);
59+
}
60+
61+
/** @param array{date: DateTime} $c */
62+
function main4(mixed $c): void{
63+
assertType('array{date: DateTime}', $c);
64+
$c['id']=1;
65+
$c['name'] = 'staabm';
66+
assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);
67+
68+
$x = (function() use (&$c) {
69+
assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);
70+
if (rand(0,1)) {
71+
$c['name'] = 'ruud';
72+
assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
73+
return 'y';
74+
}
75+
assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);
76+
return 'x';
77+
})();
78+
3979
assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c);
4080
}

0 commit comments

Comments
 (0)