|
| 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