Skip to content

Commit 8fbcf5b

Browse files
authored
More precise types in immediately invoked callables
1 parent 277e34b commit 8fbcf5b

File tree

5 files changed

+75
-4
lines changed

5 files changed

+75
-4
lines changed

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,11 @@ services:
733733
tags:
734734
- phpstan.parser.richParserNodeVisitor
735735

736+
-
737+
class: PHPStan\Parser\ImmediatelyInvokedClosureVisitor
738+
tags:
739+
- phpstan.parser.richParserNodeVisitor
740+
736741
-
737742
class: PHPStan\Parallel\ParallelAnalyser
738743
arguments:

src/Analyser/MutatingScope.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
use PHPStan\Node\Printer\ExprPrinter;
4747
use PHPStan\Node\PropertyAssignNode;
4848
use PHPStan\Parser\ArrayMapArgVisitor;
49+
use PHPStan\Parser\ImmediatelyInvokedClosureVisitor;
4950
use PHPStan\Parser\NewAssignedToPropertyVisitor;
5051
use PHPStan\Parser\Parser;
5152
use PHPStan\Php\PhpVersion;
@@ -4794,6 +4795,7 @@ private function processFinallyScopeVariableTypeHolders(
47944795
* @param Expr\ClosureUse[] $byRefUses
47954796
*/
47964797
public function processClosureScope(
4798+
Expr\Closure $expr,
47974799
self $closureScope,
47984800
?self $prevScope,
47994801
array $byRefUses,
@@ -4826,7 +4828,9 @@ public function processClosureScope(
48264828
$prevVariableType = $prevScope->getVariableType($variableName);
48274829
if (!$variableType->equals($prevVariableType)) {
48284830
$variableType = TypeCombinator::union($variableType, $prevVariableType);
4829-
$variableType = self::generalizeType($variableType, $prevVariableType, 0);
4831+
if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) !== true) {
4832+
$variableType = self::generalizeType($variableType, $prevVariableType, 0);
4833+
}
48304834
}
48314835
}
48324836

src/Analyser/NodeScopeResolver.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4232,7 +4232,7 @@ private function processClosureNode(
42324232
}
42334233

42344234
$closureScope = $scope->enterAnonymousFunction($expr, $callableParameters);
4235-
$closureScope = $closureScope->processClosureScope($scope, null, $byRefUses);
4235+
$closureScope = $closureScope->processClosureScope($expr, $scope, null, $byRefUses);
42364236
$closureType = $closureScope->getAnonymousFunctionReflection();
42374237
if (!$closureType instanceof ClosureType) {
42384238
throw new ShouldNotHappenException();
@@ -4302,7 +4302,7 @@ private function processClosureNode(
43024302
$intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope());
43034303
}
43044304
$closureScope = $scope->enterAnonymousFunction($expr, $callableParameters);
4305-
$closureScope = $closureScope->processClosureScope($intermediaryClosureScope, $prevScope, $byRefUses);
4305+
$closureScope = $closureScope->processClosureScope($expr, $intermediaryClosureScope, $prevScope, $byRefUses);
43064306
if ($closureScope->equals($prevScope)) {
43074307
break;
43084308
}
@@ -4322,7 +4322,7 @@ private function processClosureNode(
43224322
array_merge($statementResult->getImpurePoints(), $closureImpurePoints),
43234323
), $closureScope);
43244324

4325-
return new ProcessClosureResult($scope->processClosureScope($closureScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions);
4325+
return new ProcessClosureResult($scope->processClosureScope($expr, $closureScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions);
43264326
}
43274327

43284328
/**
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\NodeVisitorAbstract;
7+
8+
final class ImmediatelyInvokedClosureVisitor extends NodeVisitorAbstract
9+
{
10+
11+
public const ATTRIBUTE_NAME = 'isImmediatelyInvokedClosure';
12+
13+
public function enterNode(Node $node): ?Node
14+
{
15+
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Expr\Closure) {
16+
$node->name->setAttribute(self::ATTRIBUTE_NAME, true);
17+
}
18+
19+
return null;
20+
}
21+
22+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug11561;
4+
5+
use function PHPStan\Testing\assertType;
6+
use DateTime;
7+
8+
/** @param array{date: DateTime} $c */
9+
function main(mixed $c): void{
10+
assertType('array{date: DateTime}', $c);
11+
$c['id']=1;
12+
assertType('array{date: DateTime, id: 1}', $c);
13+
14+
$x = (function() use (&$c) {
15+
assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c);
16+
$c['name'] = 'ruud';
17+
assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
18+
return 'x';
19+
})();
20+
21+
assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c);
22+
}
23+
24+
25+
/** @param array{date: DateTime} $c */
26+
function main2(mixed $c): void{
27+
assertType('array{date: DateTime}', $c);
28+
$c['id']=1;
29+
$c['name'] = 'staabm';
30+
assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);
31+
32+
$x = (function() use (&$c) {
33+
assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c);
34+
$c['name'] = 'ruud';
35+
assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
36+
return 'x';
37+
})();
38+
39+
assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c);
40+
}

0 commit comments

Comments
 (0)