Repository navigation
Expand file tree
/
Copy pathClosureHandler.php
More file actions
104 lines (93 loc) · 3.65 KB
/
Copy pathClosureHandler.php
File metadata and controls
104 lines (93 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?php declare(strict_types = 1);
namespace PHPStan\Analyser\ExprHandler;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Stmt;
use PHPStan\Analyser\ExpressionContext;
use PHPStan\Analyser\ExpressionResult;
use PHPStan\Analyser\ExpressionResultFactory;
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ExprHandler\Helper\ClosureTypeResolver;
use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Analyser\VariableFlow;
use PHPStan\DependencyInjection\AutowiredService;
use function is_string;
/**
* @implements ExprHandler<Closure>
*/
#[AutowiredService]
final class ClosureHandler implements ExprHandler
{
public function __construct(
private ClosureTypeResolver $closureTypeResolver,
private ExpressionResultFactory $expressionResultFactory,
private DefaultNarrowingHelper $defaultNarrowingHelper,
)
{
}
public function supports(Expr $expr): bool
{
return $expr instanceof Closure;
}
public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
{
$processClosureResult = $nodeScopeResolver->processClosureNode($stmt, $expr, $scope, $storage, $nodeCallback, $context, $context->getPassedToType(), $context->getNativePassedToType());
// A plain typeCallback recursing through getClosureType() would re-walk
// the body each getType() ask before the cache populates and hang;
// ExpressionResult excludes closures from its tracked-type early return.
// Compute the ClosureType once here and store it as an eager value.
//
// The phpdoc flavour is built from the returns/yields the single body walk
// in processClosureNode() already gathered, without a second walk.
//
// A closure carries no @param/@return of its own, and its native type
// resolves the body the same way its phpdoc type does (a closure's native
// type equals its phpdoc type - e.g. a closure returning a positive-int
// method is Closure(): int<1, max> in both flavours). So the native
// flavour reuses the phpdoc ClosureType - no native walk.
$type = $this->closureTypeResolver->buildClosureTypeForClosure(
$scope,
$expr,
$processClosureResult->getGatheredReturnStatements(),
$processClosureResult->getGatheredYieldStatements(),
$processClosureResult->getExecutionEnds(),
$processClosureResult->getThrowPoints(),
$processClosureResult->getClosureTypeImpurePoints(),
$processClosureResult->getInvalidateExpressions(),
false,
$storage,
$context->getPassedToType(),
$context->getNativePassedToType(),
);
$nativeType = $type;
return $this->expressionResultFactory->create(
$processClosureResult->applyByRefUseScope($processClosureResult->getScope()),
beforeScope: $scope,
expr: $expr,
variableFlow: self::getVariableFlow($expr),
hasYield: false,
isAlwaysTerminating: false,
throwPoints: [],
impurePoints: [],
specifyTypesCallback: fn (TypeSpecifierContext $c, bool $nativeTypesPromoted) => $this->defaultNarrowingHelper->specifyDefaultTypes($expr, $c),
type: $type,
nativeType: $nativeType,
typeCallback: null,
);
}
public static function getVariableFlow(Closure $expr): ?VariableFlow
{
$uses = [];
foreach ($expr->uses as $use) {
if (!is_string($use->var->name)) {
continue;
}
$uses[] = $use->byRef ? VariableFlow::escape($use->var->name) : VariableFlow::read($use->var->name);
}
return VariableFlow::sequence(...$uses);
}
}