Skip to content

Commit 3be15da

Browse files
committed
Almost final ArrowFunctionHandler and ClosureHandler
1 parent 343d47b commit 3be15da

File tree

5 files changed

+1156
-34
lines changed

5 files changed

+1156
-34
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Generator\ExprHandler;
4+
5+
use Generator;
6+
use PhpParser\Node;
7+
use PhpParser\Node\Expr;
8+
use PhpParser\Node\Expr\ArrowFunction;
9+
use PhpParser\Node\Stmt;
10+
use PHPStan\Analyser\ExpressionContext;
11+
use PHPStan\Analyser\Generator\ExprAnalysisRequest;
12+
use PHPStan\Analyser\Generator\ExprAnalysisResult;
13+
use PHPStan\Analyser\Generator\ExprHandler;
14+
use PHPStan\Analyser\Generator\GeneratorScope;
15+
use PHPStan\Analyser\Generator\NodeCallbackRequest;
16+
use PHPStan\Analyser\Generator\TypeExprRequest;
17+
use PHPStan\Analyser\Generator\TypeExprResult;
18+
use PHPStan\Analyser\ImpurePoint;
19+
use PHPStan\Analyser\Scope;
20+
use PHPStan\Analyser\SpecifiedTypes;
21+
use PHPStan\DependencyInjection\AutowiredService;
22+
use PHPStan\Node\InArrowFunctionNode;
23+
use PHPStan\Node\InvalidateExprNode;
24+
use PHPStan\Node\PropertyAssignNode;
25+
use PHPStan\ShouldNotHappenException;
26+
use PHPStan\Type\Generic\GenericObjectType;
27+
use PHPStan\Type\IntegerType;
28+
use PHPStan\Type\MixedType;
29+
use PHPStan\Type\NullType;
30+
use PHPStan\Type\VoidType;
31+
use function array_merge;
32+
33+
/**
34+
* @implements ExprHandler<ArrowFunction>
35+
*/
36+
#[AutowiredService]
37+
final class ArrowFunctionHandler implements ExprHandler
38+
{
39+
40+
public function __construct(
41+
private ClosureHelper $closureHelper,
42+
)
43+
{
44+
}
45+
46+
public function supports(Expr $expr): bool
47+
{
48+
return $expr instanceof ArrowFunction;
49+
}
50+
51+
public function analyseExpr(
52+
Stmt $stmt,
53+
Expr $expr,
54+
GeneratorScope $scope,
55+
ExpressionContext $context,
56+
?callable $alternativeNodeCallback,
57+
): Generator
58+
{
59+
$gen = $this->processArrowFunctionNode($stmt, $expr, $scope, $alternativeNodeCallback);
60+
yield from $gen;
61+
$result = $gen->getReturn();
62+
63+
return new ExprAnalysisResult(
64+
$result->type,
65+
$result->nativeType,
66+
$result->scope,
67+
hasYield: $result->hasYield,
68+
isAlwaysTerminating: false,
69+
throwPoints: [],
70+
impurePoints: [],
71+
specifiedTruthyTypes: new SpecifiedTypes(),
72+
specifiedFalseyTypes: new SpecifiedTypes(),
73+
);
74+
}
75+
76+
/**
77+
* @param (callable(Node, Scope, callable(Node, Scope): void): void)|null $alternativeNodeCallback
78+
* @return Generator<int, ExprAnalysisRequest|TypeExprRequest|NodeCallbackRequest, ExprAnalysisResult|TypeExprResult, ExprAnalysisResult>
79+
*/
80+
public function processArrowFunctionNode(
81+
Stmt $stmt,
82+
ArrowFunction $expr,
83+
GeneratorScope $scope,
84+
?callable $alternativeNodeCallback,
85+
): Generator
86+
{
87+
/*foreach ($expr->params as $param) {
88+
$this->processParamNode($stmt, $param, $scope, $nodeCallback);
89+
}*/
90+
if ($expr->returnType !== null) {
91+
yield new NodeCallbackRequest($expr->returnType, $scope, $alternativeNodeCallback);
92+
}
93+
94+
$closureTypeGen = $this->getArrowFunctionScope($stmt, $scope, $expr);
95+
yield from $closureTypeGen;
96+
[$arrowFunctionScope, $exprResult] = $closureTypeGen->getReturn();
97+
98+
$arrowFunctionType = $arrowFunctionScope->getAnonymousFunctionReflection();
99+
if ($arrowFunctionType === null) {
100+
throw new ShouldNotHappenException();
101+
}
102+
yield new NodeCallbackRequest(new InArrowFunctionNode($arrowFunctionType, $expr), $arrowFunctionScope, $alternativeNodeCallback);
103+
104+
return new ExprAnalysisResult(
105+
$arrowFunctionType,
106+
$arrowFunctionType,
107+
$scope,
108+
hasYield: false,
109+
isAlwaysTerminating: $exprResult->isAlwaysTerminating,
110+
throwPoints: $exprResult->throwPoints,
111+
impurePoints: $exprResult->impurePoints,
112+
specifiedTruthyTypes: new SpecifiedTypes(),
113+
specifiedFalseyTypes: new SpecifiedTypes(),
114+
);
115+
}
116+
117+
/**
118+
* @return Generator<int, ExprAnalysisRequest|TypeExprRequest, ExprAnalysisResult|TypeExprResult, array{GeneratorScope, ExprAnalysisResult}>
119+
*/
120+
private function getArrowFunctionScope(Stmt $stmt, GeneratorScope $scope, ArrowFunction $node): Generator
121+
{
122+
$callableParametersGen = $this->closureHelper->createCallableParameters($node, $scope);
123+
yield from $callableParametersGen;
124+
$callableParameters = $callableParametersGen->getReturn();
125+
126+
$arrowScopeGen = $scope->enterArrowFunctionWithoutReflection($node, $callableParameters);
127+
yield from $arrowScopeGen;
128+
$arrowScope = $arrowScopeGen->getReturn();
129+
$arrowFunctionImpurePoints = [];
130+
$invalidateExpressions = [];
131+
$arrowFunctionExprResult = yield new ExprAnalysisRequest($stmt, $node->expr, $arrowScope, ExpressionContext::createDeep(), static function (Node $node, Scope $scope, callable $nodeCallback) use ($arrowScope, &$arrowFunctionImpurePoints, &$invalidateExpressions): void {
132+
$nodeCallback($node, $scope);
133+
if ($scope->getAnonymousFunctionReflection() !== $arrowScope->getAnonymousFunctionReflection()) {
134+
return;
135+
}
136+
137+
if ($node instanceof InvalidateExprNode) {
138+
$invalidateExpressions[] = $node;
139+
return;
140+
}
141+
142+
if (!$node instanceof PropertyAssignNode) {
143+
return;
144+
}
145+
146+
$arrowFunctionImpurePoints[] = new ImpurePoint(
147+
$scope,
148+
$node,
149+
'propertyAssign',
150+
'property assignment',
151+
true,
152+
);
153+
});
154+
155+
$impurePoints = array_merge($arrowFunctionImpurePoints, $arrowFunctionExprResult->impurePoints);
156+
157+
if ($node->expr instanceof Expr\Yield_ || $node->expr instanceof Expr\YieldFrom) {
158+
$yieldNode = $node->expr;
159+
160+
if ($yieldNode instanceof Expr\Yield_) {
161+
if ($yieldNode->key === null) {
162+
$keyType = new IntegerType();
163+
} else {
164+
$keyType = (yield new TypeExprRequest($yieldNode->key))->type;
165+
}
166+
167+
if ($yieldNode->value === null) {
168+
$valueType = new NullType();
169+
} else {
170+
$valueType = (yield new TypeExprRequest($yieldNode->value))->type;
171+
}
172+
} else {
173+
$yieldFromType = (yield new TypeExprRequest($yieldNode->expr))->type;
174+
$keyType = $arrowScope->getIterableKeyType($yieldFromType);
175+
$valueType = $arrowScope->getIterableValueType($yieldFromType);
176+
}
177+
178+
$returnType = new GenericObjectType(Generator::class, [
179+
$keyType,
180+
$valueType,
181+
new MixedType(),
182+
new VoidType(),
183+
]);
184+
} else {
185+
$returnType = $arrowFunctionExprResult->type; // todo keep void
186+
if ($node->returnType !== null) {
187+
$nativeReturnType = $scope->getFunctionType($node->returnType, false, false);
188+
$returnType = GeneratorScope::intersectButNotNever($nativeReturnType, $returnType);
189+
}
190+
}
191+
192+
return [
193+
$arrowScope->enterArrowFunction($this->closureHelper->createClosureType(
194+
$node,
195+
$scope,
196+
$returnType,
197+
$arrowFunctionExprResult->throwPoints,
198+
$impurePoints,
199+
$invalidateExpressions,
200+
[],
201+
)),
202+
$arrowFunctionExprResult,
203+
];
204+
}
205+
206+
}

0 commit comments

Comments
 (0)