diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 746c518953..6ccbbed977 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -2,6 +2,8 @@ namespace PHPStan\Analyser; +use PHPStan\Type\Type; + final class ExpressionResult { @@ -29,12 +31,24 @@ public function __construct( private array $impurePoints, ?callable $truthyScopeCallback = null, ?callable $falseyScopeCallback = null, + private ?Type $type = null, + private ?Type $nativeType = null, ) { $this->truthyScopeCallback = $truthyScopeCallback; $this->falseyScopeCallback = $falseyScopeCallback; } + public function getType(): ?Type + { + return $this->type; + } + + public function getNativeType(): ?Type + { + return $this->nativeType; + } + public function getScope(): MutatingScope { return $this->scope; diff --git a/src/Analyser/ExpressionResultStorage.php b/src/Analyser/ExpressionResultStorage.php new file mode 100644 index 0000000000..d41d499a47 --- /dev/null +++ b/src/Analyser/ExpressionResultStorage.php @@ -0,0 +1,36 @@ + */ + private SplObjectStorage $results; + + public function __construct() + { + $this->results = new SplObjectStorage(); + } + + public function duplicate(): self + { + $new = new self(); + $new->results->addAll($this->results); + return $new; + } + + public function storeResult(Expr $expr, ExpressionResult $result): void + { + $this->results[$expr] = $result; + } + + public function findResult(Expr $expr): ?ExpressionResult + { + return $this->results[$expr] ?? null; + } + +} diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 01d7fe7b0c..9f92cf289f 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -982,9 +982,7 @@ private function resolveType(string $exprString, Expr $node): Type } if ($this->getBooleanExpressionDepth($node->left) <= self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) { - $noopCallback = static function (): void { - }; - $leftResult = $this->nodeScopeResolver->processExprNode(new Node\Stmt\Expression($node->left), $node->left, $this, $noopCallback, ExpressionContext::createDeep()); + $leftResult = $this->nodeScopeResolver->processExprNode(new Node\Stmt\Expression($node->left), $node->left, $this, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); $rightBooleanType = $leftResult->getTruthyScope()->getType($node->right)->toBoolean(); } else { $rightBooleanType = $this->filterByTruthyValue($node->left)->getType($node->right)->toBoolean(); @@ -1014,9 +1012,7 @@ private function resolveType(string $exprString, Expr $node): Type } if ($this->getBooleanExpressionDepth($node->left) <= self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) { - $noopCallback = static function (): void { - }; - $leftResult = $this->nodeScopeResolver->processExprNode(new Node\Stmt\Expression($node->left), $node->left, $this, $noopCallback, ExpressionContext::createDeep()); + $leftResult = $this->nodeScopeResolver->processExprNode(new Node\Stmt\Expression($node->left), $node->left, $this, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); $rightBooleanType = $leftResult->getFalseyScope()->getType($node->right)->toBoolean(); } else { $rightBooleanType = $this->filterByFalseyValue($node->left)->getType($node->right)->toBoolean(); @@ -1406,6 +1402,7 @@ private function resolveType(string $exprString, Expr $node): Type new Node\Stmt\Expression($node->expr), $node->expr, $arrowScope, + new ExpressionResultStorage(), static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpurePoints, &$invalidateExpressions): void { if ($scope->getAnonymousFunctionReflection() !== $arrowScope->getAnonymousFunctionReflection()) { return; @@ -2042,9 +2039,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu } if ($node instanceof Expr\Ternary) { - $noopCallback = static function (): void { - }; - $condResult = $this->nodeScopeResolver->processExprNode(new Node\Stmt\Expression($node->cond), $node->cond, $this, $noopCallback, ExpressionContext::createDeep()); + $condResult = $this->nodeScopeResolver->processExprNode(new Node\Stmt\Expression($node->cond), $node->cond, $this, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); if ($node->if === null) { $conditionType = $this->getType($node->cond); $booleanConditionType = $conditionType->toBoolean(); @@ -5752,6 +5747,14 @@ public function debug(): array $descriptions[$key] = $nativeTypeHolder->getType()->describe(VerbosityLevel::precise()); } + foreach (array_keys($this->currentlyAssignedExpressions) as $exprString) { + $descriptions[sprintf('currently assigned %s', $exprString)] = 'true'; + } + + foreach (array_keys($this->currentlyAllowedUndefinedExpressions) as $exprString) { + $descriptions[sprintf('currently allowed undefined %s', $exprString)] = 'true'; + } + foreach ($this->conditionalExpressions as $exprString => $holders) { foreach (array_values($holders) as $i => $holder) { $key = sprintf('condition about %s #%d', $exprString, $i + 1); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a6cc26018f..53678c4d61 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -214,6 +214,7 @@ use function array_values; use function base64_decode; use function count; +use function get_class; use function in_array; use function is_array; use function is_int; @@ -316,6 +317,7 @@ public function processNodes( callable $nodeCallback, ): void { + $expressionResultStorage = new ExpressionResultStorage(); $alreadyTerminated = false; foreach ($nodes as $i => $node) { if ( @@ -325,7 +327,7 @@ public function processNodes( continue; } - $statementResult = $this->processStmtNode($node, $scope, $nodeCallback, StatementContext::createTopLevel()); + $statementResult = $this->processStmtNode($node, $scope, $expressionResultStorage, $nodeCallback, StatementContext::createTopLevel()); $scope = $statementResult->getScope(); if ($alreadyTerminated || !$statementResult->isAlwaysTerminating()) { continue; @@ -383,6 +385,7 @@ public function processStmtNodes( $parentNode, $stmts, $scope, + new ExpressionResultStorage(), $nodeCallback, $context, )->toPublic(); @@ -396,6 +399,7 @@ private function processStmtNodesInternal( Node $parentNode, array $stmts, MutatingScope $scope, + ExpressionResultStorage $storage, callable $nodeCallback, StatementContext $context, ): InternalStatementResult @@ -419,6 +423,7 @@ private function processStmtNodesInternal( $statementResult = $this->processStmtNode( $stmt, $scope, + $storage, $nodeCallback, $context, ); @@ -494,6 +499,7 @@ private function processStmtNodesInternal( private function processStmtNode( Node\Stmt $stmt, MutatingScope $scope, + ExpressionResultStorage $storage, callable $nodeCallback, StatementContext $context, ): InternalStatementResult @@ -563,7 +569,7 @@ private function processStmtNode( } if ($stmt->stmts !== null) { - $result = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $nodeCallback, $context); + $result = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $storage, $nodeCallback, $context); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -577,11 +583,11 @@ private function processStmtNode( $hasYield = false; $throwPoints = []; $impurePoints = []; - $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); + $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $storage, $nodeCallback); [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, , $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts,, $phpDocParameterOutTypes] = $this->getPhpDocs($scope, $stmt); foreach ($stmt->params as $param) { - $this->processParamNode($stmt, $param, $scope, $nodeCallback); + $this->processParamNode($stmt, $param, $scope, $storage, $nodeCallback); } if ($stmt->returnType !== null) { @@ -620,7 +626,7 @@ private function processStmtNode( $gatheredYieldStatements = []; $executionEnds = []; $functionImpurePoints = []; - $statementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $functionScope, static function (Node $node, Scope $scope) use ($nodeCallback, $functionScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$functionImpurePoints): void { + $statementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $functionScope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $functionScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$functionImpurePoints): void { $nodeCallback($node, $scope); if ($scope->getFunction() !== $functionScope->getFunction()) { return; @@ -665,11 +671,11 @@ private function processStmtNode( $hasYield = false; $throwPoints = []; $impurePoints = []; - $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); + $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $storage, $nodeCallback); [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $phpDocComment, $asserts, $selfOutType, $phpDocParameterOutTypes] = $this->getPhpDocs($scope, $stmt); foreach ($stmt->params as $param) { - $this->processParamNode($stmt, $param, $scope, $nodeCallback); + $this->processParamNode($stmt, $param, $scope, $storage, $nodeCallback); } if ($stmt->returnType !== null) { @@ -746,6 +752,7 @@ private function processStmtNode( $param->var->name, $param->hooks, $scope, + $storage, $nodeCallback, ); $methodScope = $methodScope->assignExpression(new PropertyInitializationExpr($param->var->name), new MixedType(), new MixedType()); @@ -765,7 +772,7 @@ private function processStmtNode( $gatheredYieldStatements = []; $executionEnds = []; $methodImpurePoints = []; - $statementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $methodScope, static function (Node $node, Scope $scope) use ($nodeCallback, $methodScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$methodImpurePoints): void { + $statementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $methodScope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $methodScope, &$gatheredReturnStatements, &$gatheredYieldStatements, &$executionEnds, &$methodImpurePoints): void { $nodeCallback($node, $scope); if ($scope->getFunction() !== $methodScope->getFunction()) { return; @@ -859,7 +866,7 @@ private function processStmtNode( $throwPoints = []; $isAlwaysTerminating = false; foreach ($stmt->exprs as $echoExpr) { - $result = $this->processExprNode($stmt, $echoExpr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $result = $this->processExprNode($stmt, $echoExpr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $scope = $result->getScope(); $hasYield = $hasYield || $result->hasYield(); @@ -873,7 +880,7 @@ private function processStmtNode( return new InternalStatementResult($scope, $hasYield, $isAlwaysTerminating, [], $throwPoints, $impurePoints); } elseif ($stmt instanceof Return_) { if ($stmt->expr !== null) { - $result = $this->processExprNode($stmt, $stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $result = $this->processExprNode($stmt, $stmt->expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $scope = $result->getScope(); @@ -889,7 +896,7 @@ private function processStmtNode( ], $overridingThrowPoints ?? $throwPoints, $impurePoints); } elseif ($stmt instanceof Continue_ || $stmt instanceof Break_) { if ($stmt->num !== null) { - $result = $this->processExprNode($stmt, $stmt->num, $scope, $nodeCallback, ExpressionContext::createDeep()); + $result = $this->processExprNode($stmt, $stmt->num, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -910,7 +917,7 @@ private function processStmtNode( $earlyTerminationExpr = $this->findEarlyTerminatingExpr($stmt->expr, $scope); $hasAssign = false; $currentScope = $scope; - $result = $this->processExprNode($stmt, $stmt->expr, $scope, static function (Node $node, Scope $scope) use ($nodeCallback, $currentScope, &$hasAssign): void { + $result = $this->processExprNode($stmt, $stmt->expr, $scope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $currentScope, &$hasAssign): void { $nodeCallback($node, $scope); if ($scope->getAnonymousFunctionReflection() !== $currentScope->getAnonymousFunctionReflection()) { return; @@ -957,7 +964,7 @@ private function processStmtNode( $scope = $scope->enterNamespace($stmt->name->toString()); } - $scope = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $nodeCallback, $context)->getScope(); + $scope = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $storage, $nodeCallback, $context)->getScope(); $hasYield = false; $throwPoints = []; $impurePoints = []; @@ -1008,7 +1015,7 @@ public function __invoke(Node $node, Scope $scope): void } }; - $this->processAttributeGroups($stmt, $stmt->attrGroups, $classScope, $classStatementsGathererCallback); + $this->processAttributeGroups($stmt, $stmt->attrGroups, $classScope, $storage, $classStatementsGathererCallback); $classLikeStatements = $stmt->stmts; if ($this->narrowMethodScopeFromConstructor) { @@ -1029,7 +1036,7 @@ public function __invoke(Node $node, Scope $scope): void }); } - $this->processStmtNodesInternal($stmt, $classLikeStatements, $classScope, $classStatementsGathererCallback, $context); + $this->processStmtNodesInternal($stmt, $classLikeStatements, $classScope, $storage, $classStatementsGathererCallback, $context); $nodeCallback(new ClassPropertiesNode($stmt, $this->readWritePropertiesExtensionProvider, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls(), $classStatementsGatherer->getReturnStatementsNodes(), $classStatementsGatherer->getPropertyAssigns(), $classReflection), $classScope); $nodeCallback(new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls(), $classReflection), $classScope); $nodeCallback(new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches(), $classReflection), $classScope); @@ -1039,7 +1046,7 @@ public function __invoke(Node $node, Scope $scope): void $hasYield = false; $throwPoints = []; $impurePoints = []; - $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); + $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $storage, $nodeCallback); $nativePropertyType = $stmt->type !== null ? ParserNodeTypeToPHPStanType::resolve($stmt->type, $scope->getClassReflection()) : null; @@ -1052,7 +1059,7 @@ public function __invoke(Node $node, Scope $scope): void foreach ($stmt->props as $prop) { $nodeCallback($prop, $scope); if ($prop->default !== null) { - $this->processExprNode($stmt, $prop->default, $scope, $nodeCallback, ExpressionContext::createDeep()); + $this->processExprNode($stmt, $prop->default, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); } if (!$scope->isInClass()) { @@ -1101,6 +1108,7 @@ public function __invoke(Node $node, Scope $scope): void $propertyName, $stmt->hooks, $scope, + $storage, $nodeCallback, ); } @@ -1111,7 +1119,7 @@ public function __invoke(Node $node, Scope $scope): void } elseif ($stmt instanceof If_) { $conditionType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean(); $ifAlwaysTrue = $conditionType->isTrue()->yes(); - $condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); + $condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $exitPoints = []; $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); @@ -1120,7 +1128,7 @@ public function __invoke(Node $node, Scope $scope): void $alwaysTerminating = true; $hasYield = $condResult->hasYield(); - $branchScopeStatementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $condResult->getTruthyScope(), $nodeCallback, $context); + $branchScopeStatementResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $condResult->getTruthyScope(), $storage, $nodeCallback, $context); if (!$conditionType->isTrue()->no()) { $exitPoints = $branchScopeStatementResult->getExitPoints(); @@ -1146,11 +1154,11 @@ public function __invoke(Node $node, Scope $scope): void foreach ($stmt->elseifs as $elseif) { $nodeCallback($elseif, $scope); $elseIfConditionType = ($this->treatPhpDocTypesAsCertain ? $condScope->getType($elseif->cond) : $scope->getNativeType($elseif->cond))->toBoolean(); - $condResult = $this->processExprNode($stmt, $elseif->cond, $condScope, $nodeCallback, ExpressionContext::createDeep()); + $condResult = $this->processExprNode($stmt, $elseif->cond, $condScope, $storage, $nodeCallback, ExpressionContext::createDeep()); $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $condResult->getImpurePoints()); $condScope = $condResult->getScope(); - $branchScopeStatementResult = $this->processStmtNodesInternal($elseif, $elseif->stmts, $condResult->getTruthyScope(), $nodeCallback, $context); + $branchScopeStatementResult = $this->processStmtNodesInternal($elseif, $elseif->stmts, $condResult->getTruthyScope(), $storage, $nodeCallback, $context); if ( !$ifAlwaysTrue @@ -1190,7 +1198,7 @@ public function __invoke(Node $node, Scope $scope): void } } else { $nodeCallback($stmt->else, $scope); - $branchScopeStatementResult = $this->processStmtNodesInternal($stmt->else, $stmt->else->stmts, $scope, $nodeCallback, $context); + $branchScopeStatementResult = $this->processStmtNodesInternal($stmt->else, $stmt->else->stmts, $scope, $storage, $nodeCallback, $context); if (!$ifAlwaysTrue && !$lastElseIfConditionIsTrue) { $exitPoints = array_merge($exitPoints, $branchScopeStatementResult->getExitPoints()); @@ -1223,9 +1231,9 @@ public function __invoke(Node $node, Scope $scope): void $hasYield = false; $throwPoints = []; $impurePoints = []; - $this->processTraitUse($stmt, $scope, $nodeCallback); + $this->processTraitUse($stmt, $scope, $storage->duplicate(), $nodeCallback); } elseif ($stmt instanceof Foreach_) { - $condResult = $this->processExprNode($stmt, $stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $condResult = $this->processExprNode($stmt, $stmt->expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); $scope = $condResult->getScope(); @@ -1248,16 +1256,18 @@ public function __invoke(Node $node, Scope $scope): void $nodeCallback(new VariableAssignNode($stmt->valueVar, new GetIterableValueTypeExpr($stmt->expr)), $originalScope); } + $originalStorage = $storage; + $storage = $originalStorage->duplicate(); if ($context->isTopLevel()) { $originalScope = $this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope; - $bodyScope = $this->enterForeach($originalScope, $originalScope, $stmt, $nodeCallback); + $bodyScope = $this->enterForeach($originalScope, $storage, $originalScope, $stmt, $nodeCallback); $count = 0; do { $prevScope = $bodyScope; $bodyScope = $bodyScope->mergeWith($this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope); - $bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt, $nodeCallback); - $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, static function (): void { - }, $context->enterDeep())->filterOutLoopExitPoints(); + $storage = $originalStorage->duplicate(); + $bodyScope = $this->enterForeach($bodyScope, $storage, $originalScope, $stmt, $nodeCallback); + $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints(); $bodyScope = $bodyScopeResult->getScope(); foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); @@ -1274,8 +1284,9 @@ public function __invoke(Node $node, Scope $scope): void } $bodyScope = $bodyScope->mergeWith($this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope); - $bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt, $nodeCallback); - $finalScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); + $storage = $originalStorage; + $bodyScope = $this->enterForeach($bodyScope, $storage, $originalScope, $stmt, $nodeCallback); + $finalScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints(); $finalScope = $finalScopeResult->getScope(); $scopesWithIterableValueType = []; @@ -1409,8 +1420,9 @@ public function __invoke(Node $node, Scope $scope): void $impurePoints, ); } elseif ($stmt instanceof While_) { - $condResult = $this->processExprNode($stmt, $stmt->cond, $scope, static function (): void { - }, ExpressionContext::createDeep()); + $originalStorage = $storage; + $storage = $originalStorage->duplicate(); + $condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep()); $beforeCondBooleanType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean(); $condScope = $condResult->getFalseyScope(); if (!$context->isTopLevel() && $beforeCondBooleanType->isFalse()->yes()) { @@ -1434,10 +1446,9 @@ public function __invoke(Node $node, Scope $scope): void do { $prevScope = $bodyScope; $bodyScope = $bodyScope->mergeWith($scope); - $bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, static function (): void { - }, ExpressionContext::createDeep())->getTruthyScope(); - $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, static function (): void { - }, $context->enterDeep())->filterOutLoopExitPoints(); + $storage = $originalStorage->duplicate(); + $bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep())->getTruthyScope(); + $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints(); $bodyScope = $bodyScopeResult->getScope(); foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); @@ -1455,8 +1466,9 @@ public function __invoke(Node $node, Scope $scope): void $bodyScope = $bodyScope->mergeWith($scope); $bodyScopeMaybeRan = $bodyScope; - $bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); - $finalScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); + $storage = $originalStorage; + $bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); + $finalScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints(); $finalScope = $finalScopeResult->getScope()->filterByFalseyValue($stmt->cond); $condBooleanType = ($this->treatPhpDocTypesAsCertain ? $bodyScopeMaybeRan->getType($stmt->cond) : $bodyScopeMaybeRan->getNativeType($stmt->cond))->toBoolean(); @@ -1512,13 +1524,14 @@ public function __invoke(Node $node, Scope $scope): void $hasYield = false; $throwPoints = []; $impurePoints = []; + $originalStorage = $storage; if ($context->isTopLevel()) { do { $prevScope = $bodyScope; $bodyScope = $bodyScope->mergeWith($scope); - $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, static function (): void { - }, $context->enterDeep())->filterOutLoopExitPoints(); + $storage = $originalStorage->duplicate(); + $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints(); $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); $bodyScope = $bodyScopeResult->getScope(); foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { @@ -1528,8 +1541,7 @@ public function __invoke(Node $node, Scope $scope): void foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); } - $bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, static function (): void { - }, ExpressionContext::createDeep())->getTruthyScope(); + $bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep())->getTruthyScope(); if ($bodyScope->equals($prevScope)) { break; } @@ -1543,7 +1555,8 @@ public function __invoke(Node $node, Scope $scope): void $bodyScope = $bodyScope->mergeWith($scope); } - $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); + $storage = $originalStorage; + $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints(); $bodyScope = $bodyScopeResult->getScope(); foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); @@ -1563,13 +1576,13 @@ public function __invoke(Node $node, Scope $scope): void $finalScope = $scope; } if (!$alwaysTerminating) { - $condResult = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep()); + $condResult = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep()); $hasYield = $condResult->hasYield(); $throwPoints = $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); $finalScope = $condResult->getFalseyScope(); } else { - $this->processExprNode($stmt, $stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep()); + $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep()); } foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); @@ -1589,19 +1602,21 @@ public function __invoke(Node $node, Scope $scope): void $throwPoints = []; $impurePoints = []; foreach ($stmt->init as $initExpr) { - $initResult = $this->processExprNode($stmt, $initExpr, $initScope, $nodeCallback, ExpressionContext::createTopLevel()); + $initResult = $this->processExprNode($stmt, $initExpr, $initScope, $storage, $nodeCallback, ExpressionContext::createTopLevel()); $initScope = $initResult->getScope(); $hasYield = $hasYield || $initResult->hasYield(); $throwPoints = array_merge($throwPoints, $initResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $initResult->getImpurePoints()); } + $originalStorage = $storage; + $storage = $originalStorage->duplicate(); + $bodyScope = $initScope; $isIterableAtLeastOnce = TrinaryLogic::createYes(); $lastCondExpr = array_last($stmt->cond) ?? null; foreach ($stmt->cond as $condExpr) { - $condResult = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void { - }, ExpressionContext::createDeep()); + $condResult = $this->processExprNode($stmt, $condExpr, $bodyScope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep()); $initScope = $condResult->getScope(); $condResultScope = $condResult->getScope(); @@ -1622,21 +1637,19 @@ public function __invoke(Node $node, Scope $scope): void $count = 0; do { $prevScope = $bodyScope; + $storage = $originalStorage->duplicate(); $bodyScope = $bodyScope->mergeWith($initScope); if ($lastCondExpr !== null) { - $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, static function (): void { - }, ExpressionContext::createDeep())->getTruthyScope(); + $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep())->getTruthyScope(); } - $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, static function (): void { - }, $context->enterDeep())->filterOutLoopExitPoints(); + $bodyScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, new NoopNodeCallback(), $context->enterDeep())->filterOutLoopExitPoints(); $bodyScope = $bodyScopeResult->getScope(); foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); } foreach ($stmt->loop as $loopExpr) { - $exprResult = $this->processExprNode($stmt, $loopExpr, $bodyScope, static function (): void { - }, ExpressionContext::createTopLevel()); + $exprResult = $this->processExprNode($stmt, $loopExpr, $bodyScope, $storage, new NoopNodeCallback(), ExpressionContext::createTopLevel()); $bodyScope = $exprResult->getScope(); $hasYield = $hasYield || $exprResult->hasYield(); $throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); @@ -1654,16 +1667,17 @@ public function __invoke(Node $node, Scope $scope): void } while ($count < self::LOOP_SCOPE_ITERATIONS); } + $storage = $originalStorage; $bodyScope = $bodyScope->mergeWith($initScope); $alwaysIterates = TrinaryLogic::createFromBoolean($context->isTopLevel()); if ($lastCondExpr !== null) { $alwaysIterates = $alwaysIterates->and($bodyScope->getType($lastCondExpr)->toBoolean()->isTrue()); - $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); + $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); $bodyScope = $this->inferForLoopExpressions($stmt, $lastCondExpr, $bodyScope); } - $finalScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); + $finalScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $bodyScope, $storage, $nodeCallback, $context)->filterOutLoopExitPoints(); $finalScope = $finalScopeResult->getScope(); foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); @@ -1671,7 +1685,7 @@ public function __invoke(Node $node, Scope $scope): void $loopScope = $finalScope; foreach ($stmt->loop as $loopExpr) { - $loopScope = $this->processExprNode($stmt, $loopExpr, $loopScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); + $loopScope = $this->processExprNode($stmt, $loopExpr, $loopScope, $storage, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); } $finalScope = $finalScope->generalizeWith($loopScope); @@ -1719,7 +1733,7 @@ public function __invoke(Node $node, Scope $scope): void array_merge($impurePoints, $finalScopeResult->getImpurePoints()), ); } elseif ($stmt instanceof Switch_) { - $condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $nodeCallback, ExpressionContext::createDeep()); + $condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $condResult->getScope(); $scopeForBranches = $scope; $finalScope = null; @@ -1735,7 +1749,7 @@ public function __invoke(Node $node, Scope $scope): void if ($caseNode->cond !== null) { $condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond); $fullCondExpr = $fullCondExpr === null ? $condExpr : new BooleanOr($fullCondExpr, $condExpr); - $caseResult = $this->processExprNode($stmt, $caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep()); + $caseResult = $this->processExprNode($stmt, $caseNode->cond, $scopeForBranches, $storage, $nodeCallback, ExpressionContext::createDeep()); $scopeForBranches = $caseResult->getScope(); $hasYield = $hasYield || $caseResult->hasYield(); $throwPoints = array_merge($throwPoints, $caseResult->getThrowPoints()); @@ -1748,7 +1762,7 @@ public function __invoke(Node $node, Scope $scope): void } $branchScope = $branchScope->mergeWith($prevScope); - $branchScopeResult = $this->processStmtNodesInternal($caseNode, $caseNode->stmts, $branchScope, $nodeCallback, $context); + $branchScopeResult = $this->processStmtNodesInternal($caseNode, $caseNode->stmts, $branchScope, $storage, $nodeCallback, $context); $branchScope = $branchScopeResult->getScope(); $branchFinalScopeResult = $branchScopeResult->filterOutLoopExitPoints(); $hasYield = $hasYield || $branchFinalScopeResult->hasYield(); @@ -1794,7 +1808,7 @@ public function __invoke(Node $node, Scope $scope): void return new InternalStatementResult($finalScope, $hasYield, $alwaysTerminating, $exitPointsForOuterLoop, $throwPoints, $impurePoints); } elseif ($stmt instanceof TryCatch) { - $branchScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $nodeCallback, $context); + $branchScopeResult = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $storage, $nodeCallback, $context); $branchScope = $branchScopeResult->getScope(); $finalScope = $branchScopeResult->isAlwaysTerminating() ? null : $branchScope; @@ -1948,7 +1962,7 @@ public function __invoke(Node $node, Scope $scope): void $variableName = $catchNode->var->name; } - $catchScopeResult = $this->processStmtNodesInternal($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $nodeCallback, $context); + $catchScopeResult = $this->processStmtNodesInternal($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $storage, $nodeCallback, $context); $catchScopeForFinally = $catchScopeResult->getScope(); $finalScope = $catchScopeResult->isAlwaysTerminating() ? $finalScope : $catchScopeResult->getScope()->mergeWith($finalScope); @@ -1993,7 +2007,7 @@ public function __invoke(Node $node, Scope $scope): void if ($finallyScope !== null) { $originalFinallyScope = $finallyScope; - $finallyResult = $this->processStmtNodesInternal($stmt->finally, $stmt->finally->stmts, $finallyScope, $nodeCallback, $context); + $finallyResult = $this->processStmtNodesInternal($stmt->finally, $stmt->finally->stmts, $finallyScope, $storage, $nodeCallback, $context); $alwaysTerminating = $alwaysTerminating || $finallyResult->isAlwaysTerminating(); $hasYield = $hasYield || $finallyResult->hasYield(); $throwPointsForLater = array_merge($throwPointsForLater, $finallyResult->getThrowPoints()); @@ -2016,7 +2030,7 @@ public function __invoke(Node $node, Scope $scope): void $impurePoints = []; foreach ($stmt->vars as $var) { $scope = $this->lookForSetAllowedUndefinedExpressions($scope, $var); - $exprResult = $this->processExprNode($stmt, $var, $scope, $nodeCallback, ExpressionContext::createDeep()); + $exprResult = $this->processExprNode($stmt, $var, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $exprResult->getScope(); $scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $var); $hasYield = $hasYield || $exprResult->hasYield(); @@ -2046,7 +2060,7 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch /** @var Expr $clonedVar */ [$clonedVar] = $traverser->traverse([$clonedVar]); - $scope = $this->processVirtualAssign($scope, $stmt, $clonedVar, new UnsetOffsetExpr($var->var, $var->dim), $nodeCallback)->getScope(); + $scope = $this->processVirtualAssign($scope, $storage, $stmt, $clonedVar, new UnsetOffsetExpr($var->var, $var->dim), $nodeCallback)->getScope(); } elseif ($var instanceof PropertyFetch) { $scope = $scope->invalidateExpression($var); $impurePoints[] = new ImpurePoint( @@ -2087,7 +2101,7 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch throw new ShouldNotHappenException(); } $scope = $this->lookForSetAllowedUndefinedExpressions($scope, $var); - $varResult = $this->processExprNode($stmt, $var, $scope, $nodeCallback, ExpressionContext::createDeep()); + $varResult = $this->processExprNode($stmt, $var, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $impurePoints = array_merge($impurePoints, $varResult->getImpurePoints()); $scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $var); @@ -2119,12 +2133,12 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch } if ($var->default !== null) { - $defaultExprResult = $this->processExprNode($stmt, $var->default, $scope, $nodeCallback, ExpressionContext::createDeep()); + $defaultExprResult = $this->processExprNode($stmt, $var->default, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $impurePoints = array_merge($impurePoints, $defaultExprResult->getImpurePoints()); } $scope = $scope->enterExpressionAssign($var->var); - $varResult = $this->processExprNode($stmt, $var->var, $scope, $nodeCallback, ExpressionContext::createDeep()); + $varResult = $this->processExprNode($stmt, $var->var, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $impurePoints = array_merge($impurePoints, $varResult->getImpurePoints()); $scope = $scope->exitExpressionAssign($var->var); @@ -2139,7 +2153,7 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch $impurePoints = []; foreach ($stmt->consts as $const) { $nodeCallback($const, $scope); - $constResult = $this->processExprNode($stmt, $const->value, $scope, $nodeCallback, ExpressionContext::createDeep()); + $constResult = $this->processExprNode($stmt, $const->value, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $impurePoints = array_merge($impurePoints, $constResult->getImpurePoints()); if ($const->namespacedName !== null) { $constantName = new Name\FullyQualified($const->namespacedName->toString()); @@ -2152,10 +2166,10 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch $hasYield = false; $throwPoints = []; $impurePoints = []; - $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); + $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $storage, $nodeCallback); foreach ($stmt->consts as $const) { $nodeCallback($const, $scope); - $constResult = $this->processExprNode($stmt, $const->value, $scope, $nodeCallback, ExpressionContext::createDeep()); + $constResult = $this->processExprNode($stmt, $const->value, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $impurePoints = array_merge($impurePoints, $constResult->getImpurePoints()); if ($scope->getClassReflection() === null) { throw new ShouldNotHappenException(); @@ -2169,10 +2183,10 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch } elseif ($stmt instanceof Node\Stmt\EnumCase) { $hasYield = false; $throwPoints = []; - $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); + $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $storage, $nodeCallback); $impurePoints = []; if ($stmt->expr !== null) { - $exprResult = $this->processExprNode($stmt, $stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $exprResult = $this->processExprNode($stmt, $stmt->expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $impurePoints = $exprResult->getImpurePoints(); } } elseif ($stmt instanceof InlineHTML) { @@ -2182,7 +2196,7 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch new ImpurePoint($scope, $stmt, 'betweenPhpTags', 'output between PHP opening and closing tags', true), ]; } elseif ($stmt instanceof Node\Stmt\Block) { - $result = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $nodeCallback, $context); + $result = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $storage, $nodeCallback, $context); if ($this->polluteScopeWithBlock) { return $result; } @@ -2512,7 +2526,14 @@ private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr /** * @param callable(Node $node, Scope $scope): void $nodeCallback */ - public function processExprNode(Node\Stmt $stmt, Expr $expr, MutatingScope $scope, callable $nodeCallback, ExpressionContext $context): ExpressionResult + public function processExprNode( + Node\Stmt $stmt, + Expr $expr, + MutatingScope $scope, + ExpressionResultStorage $storage, + callable $nodeCallback, + ExpressionContext $context, + ): ExpressionResult { if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { if ($expr instanceof FuncCall) { @@ -2527,30 +2548,42 @@ public function processExprNode(Node\Stmt $stmt, Expr $expr, MutatingScope $scop throw new ShouldNotHappenException(); } - return $this->processExprNode($stmt, $newExpr, $scope, $nodeCallback, $context); + return $this->processExprNode($stmt, $newExpr, $scope, $storage, $nodeCallback, $context); + } + + $existingExprResult = $storage->findResult($expr); + if ($existingExprResult !== null) { + if ($nodeCallback instanceof ShallowNodeCallback) { + return $existingExprResult; + } + throw new ShouldNotHappenException(sprintf('Expr %s on line %d has already been analysed', get_class($expr), $expr->getStartLine())); } $this->callNodeCallbackWithExpression($nodeCallback, $expr, $scope, $context); + $expressionType = null; + $expressionNativeType = null; + if ($expr instanceof Variable) { $hasYield = false; $throwPoints = []; $impurePoints = []; $isAlwaysTerminating = false; if ($expr->name instanceof Expr) { - return $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); + return $this->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); } elseif (in_array($expr->name, Scope::SUPERGLOBAL_VARIABLES, true)) { $impurePoints[] = new ImpurePoint($scope, $expr, 'superglobal', 'access to superglobal variable', true); } } elseif ($expr instanceof Assign || $expr instanceof AssignRef) { $result = $this->processAssignVar( $scope, + $storage, $stmt, $expr->var, $expr->expr, $nodeCallback, $context, - function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): ExpressionResult { + function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $storage): ExpressionResult { $impurePoints = []; if ($expr instanceof AssignRef) { $referencedExpr = $expr->expr; @@ -2578,7 +2611,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp ); } - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -2589,11 +2622,15 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp $scope = $scope->exitExpressionAssign($expr->expr); } - return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + $result = new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints, type: $result->getType(), nativeType: $result->getNativeType()); + $storage->storeResult($expr, $result); + + return $result; }, true, ); $scope = $result->getScope(); + $storage->storeResult($expr, $result); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); @@ -2609,12 +2646,13 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp } elseif ($expr instanceof Expr\AssignOp) { $result = $this->processAssignVar( $scope, + $storage, $stmt, $expr->var, $expr, $nodeCallback, $context, - function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): ExpressionResult { + function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $storage): ExpressionResult { $originalScope = $scope; if ($expr instanceof Expr\AssignOp\Coalesce) { $scope = $scope->filterByFalseyValue( @@ -2622,15 +2660,18 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp ); } - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); if ($expr instanceof Expr\AssignOp\Coalesce) { - return new ExpressionResult( + $result = new ExpressionResult( $result->getScope()->mergeWith($originalScope), $result->hasYield(), $result->isAlwaysTerminating(), $result->getThrowPoints(), $result->getImpurePoints(), ); + $storage->storeResult($expr, $result); + + return $result; } return $result; @@ -2638,6 +2679,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp $expr instanceof Expr\AssignOp\Coalesce, ); $scope = $result->getScope(); + $storage->storeResult($expr, $result); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); @@ -2665,27 +2707,17 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp ); } - $nameResult = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); + $nameResult = $this->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $nameResult->getScope(); $throwPoints = $nameResult->getThrowPoints(); $impurePoints = $nameResult->getImpurePoints(); $isAlwaysTerminating = $nameResult->isAlwaysTerminating(); - if ( + if ( // phpcs:ignore $nameType->isObject()->yes() && $nameType->isCallable()->yes() && (new ObjectType(Closure::class))->isSuperTypeOf($nameType)->no() ) { - $invokeResult = $this->processExprNode( - $stmt, - new MethodCall($expr->name, '__invoke', $expr->getArgs(), $expr->getAttributes()), - $scope, - static function (): void { - }, - $context->enterDeep(), - ); - $throwPoints = array_merge($throwPoints, $invokeResult->getThrowPoints()); - $impurePoints = array_merge($impurePoints, $invokeResult->getImpurePoints()); - $isAlwaysTerminating = $invokeResult->isAlwaysTerminating(); + // processed later } elseif ($parametersAcceptor instanceof CallableParametersAcceptor) { $callableThrowPoints = array_map(static fn (SimpleThrowPoint $throwPoint) => $throwPoint->isExplicit() ? InternalThrowPoint::createExplicit($scope, $throwPoint->getType(), $expr, $throwPoint->canContainAnyThrowable()) : InternalThrowPoint::createImplicit($scope, $expr), $parametersAcceptor->getThrowPoints()); if (!$this->implicitThrows) { @@ -2741,6 +2773,7 @@ static function (): void { if (count($clonePropertyKeyTypeScalars) === 1) { $this->processVirtualAssign( $scope, + $storage, $stmt, new PropertyFetch($cloneExpr, (string) $clonePropertyKeyTypeScalars[0], $propertyAttributes), new TypeExpr($clonePropertiesArgTypeConstantArray->getValueTypes()[$i]), @@ -2751,6 +2784,7 @@ static function (): void { $this->processVirtualAssign( $scope, + $storage, $stmt, new PropertyFetch($cloneExpr, new TypeExpr($clonePropertyKeyType), $propertyAttributes), new TypeExpr($clonePropertiesArgTypeConstantArray->getValueTypes()[$i]), @@ -2760,13 +2794,34 @@ static function (): void { } } - $result = $this->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context); + $result = $this->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $expr, $scope, $storage, $nodeCallback, $context); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); $isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); + if ($expr->name instanceof Expr) { + $nameType = $scope->getType($expr->name); + if ( + $nameType->isObject()->yes() + && $nameType->isCallable()->yes() + && (new ObjectType(Closure::class))->isSuperTypeOf($nameType)->no() + ) { + $invokeResult = $this->processExprNode( + $stmt, + new MethodCall($expr->name, '__invoke', $expr->getArgs(), $expr->getAttributes()), + $scope, + $storage, + new NoopNodeCallback(), + $context->enterDeep(), + ); + $throwPoints = array_merge($throwPoints, $invokeResult->getThrowPoints()); + $impurePoints = array_merge($impurePoints, $invokeResult->getImpurePoints()); + $isAlwaysTerminating = $invokeResult->isAlwaysTerminating(); + } + } + if ($functionReflection !== null) { $functionThrowPoint = $this->getFunctionThrowPoint($functionReflection, $parametersAcceptor, $expr, $scope); if ($functionThrowPoint !== null) { @@ -2815,6 +2870,7 @@ static function (): void { $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $arrayArg, new NativeTypeExpr( @@ -2834,6 +2890,7 @@ static function (): void { $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $arrayArg, new NativeTypeExpr( @@ -2859,6 +2916,7 @@ static function (): void { $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $arrayArg, new NativeTypeExpr($scope->getType($arrayArg)->shuffleArray(), $scope->getNativeType($arrayArg)->shuffleArray()), @@ -2881,6 +2939,7 @@ static function (): void { $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $arrayArg, new NativeTypeExpr( @@ -2900,6 +2959,7 @@ static function (): void { $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $arrayArg, new NativeTypeExpr($this->getArraySortPreserveListFunctionType($scope->getType($arrayArg)), $this->getArraySortPreserveListFunctionType($scope->getNativeType($arrayArg))), @@ -2916,6 +2976,7 @@ static function (): void { $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $arrayArg, new NativeTypeExpr($this->getArraySortDoNotPreserveListFunctionType($scope->getType($arrayArg)), $this->getArraySortDoNotPreserveListFunctionType($scope->getNativeType($arrayArg))), @@ -2992,7 +3053,7 @@ static function (): void { ); } - $result = $this->processExprNode($stmt, $expr->var, $closureCallScope ?? $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->var, $closureCallScope ?? $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); @@ -3005,7 +3066,7 @@ static function (): void { $methodReflection = null; $calledOnType = $scope->getType($expr->var); if ($expr->name instanceof Expr) { - $methodNameResult = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); + $methodNameResult = $this->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = array_merge($throwPoints, $methodNameResult->getThrowPoints()); $scope = $methodNameResult->getScope(); } else { @@ -3054,6 +3115,7 @@ static function (): void { $parametersAcceptor, $expr, $scope, + $storage, $nodeCallback, $context, ); @@ -3106,10 +3168,10 @@ static function (): void { $isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); } elseif ($expr instanceof Expr\NullsafeMethodCall) { $nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var); - $exprResult = $this->processExprNode($stmt, new MethodCall($expr->var, $expr->name, $expr->args, array_merge($expr->getAttributes(), ['virtualNullsafeMethodCall' => true])), $nonNullabilityResult->getScope(), $nodeCallback, $context); + $exprResult = $this->processExprNode($stmt, new MethodCall($expr->var, $expr->name, $expr->args, array_merge($expr->getAttributes(), ['virtualNullsafeMethodCall' => true])), $nonNullabilityResult->getScope(), $storage, $nodeCallback, $context); $scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); - return new ExpressionResult( + $result = new ExpressionResult( $scope, $exprResult->hasYield(), false, @@ -3118,38 +3180,28 @@ static function (): void { static fn (): MutatingScope => $scope->filterByTruthyValue($expr), static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); + $storage->storeResult($expr, $result); + + return $result; } elseif ($expr instanceof StaticCall) { $hasYield = false; $throwPoints = []; $impurePoints = []; $isAlwaysTerminating = false; if ($expr->class instanceof Expr) { - $objectClasses = $scope->getType($expr->class)->getObjectClassNames(); - if (count($objectClasses) !== 1) { - $objectClasses = $scope->getType(new New_($expr->class))->getObjectClassNames(); - } - if (count($objectClasses) === 1) { - $objectExprResult = $this->processExprNode($stmt, new StaticCall(new Name($objectClasses[0]), $expr->name, []), $scope, static function (): void { - }, $context->enterDeep()); - $additionalThrowPoints = $objectExprResult->getThrowPoints(); - } else { - $additionalThrowPoints = [InternalThrowPoint::createImplicit($scope, $expr)]; - } - $classResult = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); + $classResult = $this->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $classResult->hasYield(); $throwPoints = array_merge($throwPoints, $classResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $classResult->getImpurePoints()); $isAlwaysTerminating = $classResult->isAlwaysTerminating(); - foreach ($additionalThrowPoints as $throwPoint) { - $throwPoints[] = $throwPoint; - } + $scope = $classResult->getScope(); } $parametersAcceptor = null; $methodReflection = null; if ($expr->name instanceof Expr) { - $result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -3218,6 +3270,22 @@ static function (): void { } } + if ($expr->class instanceof Expr) { + $objectClasses = $scope->getType($expr->class)->getObjectClassNames(); + if (count($objectClasses) !== 1) { + $objectClasses = $scope->getType(new New_($expr->class))->getObjectClassNames(); + } + if (count($objectClasses) === 1) { + $objectExprResult = $this->processExprNode($stmt, new StaticCall(new Name($objectClasses[0]), $expr->name, []), $scope, $storage, new NoopNodeCallback(), $context->enterDeep()); + $additionalThrowPoints = $objectExprResult->getThrowPoints(); + } else { + $additionalThrowPoints = [InternalThrowPoint::createImplicit($scope, $expr)]; + } + foreach ($additionalThrowPoints as $throwPoint) { + $throwPoints[] = $throwPoint; + } + } + if ($methodReflection !== null) { $impurePoint = SimpleImpurePoint::createFromVariant($methodReflection, $parametersAcceptor, $scope, $expr->getArgs()); if ($impurePoint !== null) { @@ -3238,7 +3306,7 @@ static function (): void { $returnType = $parametersAcceptor->getReturnType(); $isAlwaysTerminating = $returnType instanceof NeverType && $returnType->isExplicit(); } - $result = $this->processArgs($stmt, $methodReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context, $closureBindScope ?? null); + $result = $this->processArgs($stmt, $methodReflection, null, $parametersAcceptor, $expr, $scope, $storage, $nodeCallback, $context, $closureBindScope ?? null); $scope = $result->getScope(); $scopeFunction = $scope->getFunction(); @@ -3283,14 +3351,14 @@ static function (): void { $isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); } elseif ($expr instanceof PropertyFetch) { $scopeBeforeVar = $scope; - $result = $this->processExprNode($stmt, $expr->var, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $isAlwaysTerminating = $result->isAlwaysTerminating(); $scope = $result->getScope(); if ($expr->name instanceof Expr) { - $result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -3313,10 +3381,10 @@ static function (): void { } } elseif ($expr instanceof Expr\NullsafePropertyFetch) { $nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var); - $exprResult = $this->processExprNode($stmt, new PropertyFetch($expr->var, $expr->name, array_merge($expr->getAttributes(), ['virtualNullsafePropertyFetch' => true])), $nonNullabilityResult->getScope(), $nodeCallback, $context); + $exprResult = $this->processExprNode($stmt, new PropertyFetch($expr->var, $expr->name, array_merge($expr->getAttributes(), ['virtualNullsafePropertyFetch' => true])), $nonNullabilityResult->getScope(), $storage, $nodeCallback, $context); $scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); - return new ExpressionResult( + $result = new ExpressionResult( $scope, $exprResult->hasYield(), false, @@ -3325,6 +3393,9 @@ static function (): void { static fn (): MutatingScope => $scope->filterByTruthyValue($expr), static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); + $storage->storeResult($expr, $result); + + return $result; } elseif ($expr instanceof StaticPropertyFetch) { $hasYield = false; $throwPoints = []; @@ -3339,7 +3410,7 @@ static function (): void { ]; $isAlwaysTerminating = false; if ($expr->class instanceof Expr) { - $result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); @@ -3347,7 +3418,7 @@ static function (): void { $scope = $result->getScope(); } if ($expr->name instanceof Expr) { - $result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -3355,26 +3426,32 @@ static function (): void { $scope = $result->getScope(); } } elseif ($expr instanceof Expr\Closure) { - $processClosureResult = $this->processClosureNode($stmt, $expr, $scope, $nodeCallback, $context, null); + $processClosureResult = $this->processClosureNode($stmt, $expr, $scope, $storage, $nodeCallback, $context, null); + $scope = $processClosureResult->getScope(); - return new ExpressionResult( - $processClosureResult->getScope(), + $result = new ExpressionResult( + $scope, false, false, [], [], ); + $storage->storeResult($expr, $result); + + return $result; } elseif ($expr instanceof Expr\ArrowFunction) { - $result = $this->processArrowFunctionNode($stmt, $expr, $scope, $nodeCallback, null); - return new ExpressionResult( + $result = $this->processArrowFunctionNode($stmt, $expr, $scope, $storage, $nodeCallback, null); + $exprResult = new ExpressionResult( $result->getScope(), $result->hasYield(), false, [], [], ); + $storage->storeResult($expr, $exprResult); + return $exprResult; } elseif ($expr instanceof ErrorSuppress) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); @@ -3390,7 +3467,7 @@ static function (): void { ]; $isAlwaysTerminating = true; if ($expr->expr !== null) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -3405,7 +3482,7 @@ static function (): void { if (!$part instanceof Expr) { continue; } - $result = $this->processExprNode($stmt, $part, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $part, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -3418,7 +3495,7 @@ static function (): void { $impurePoints = []; $isAlwaysTerminating = false; if ($expr->dim !== null) { - $result = $this->processExprNode($stmt, $expr->dim, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->dim, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); @@ -3426,7 +3503,7 @@ static function (): void { $scope = $result->getScope(); } - $result = $this->processExprNode($stmt, $expr->var, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -3442,7 +3519,7 @@ static function (): void { $itemNodes[] = new LiteralArrayItem($scope, $arrayItem); $nodeCallback($arrayItem, $scope); if ($arrayItem->key !== null) { - $keyResult = $this->processExprNode($stmt, $arrayItem->key, $scope, $nodeCallback, $context->enterDeep()); + $keyResult = $this->processExprNode($stmt, $arrayItem->key, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $keyResult->hasYield(); $throwPoints = array_merge($throwPoints, $keyResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints()); @@ -3450,7 +3527,7 @@ static function (): void { $scope = $keyResult->getScope(); } - $valueResult = $this->processExprNode($stmt, $arrayItem->value, $scope, $nodeCallback, $context->enterDeep()); + $valueResult = $this->processExprNode($stmt, $arrayItem->value, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $valueResult->hasYield(); $throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); @@ -3459,8 +3536,8 @@ static function (): void { } $nodeCallback(new LiteralArrayNode($expr, $itemNodes), $scope); } elseif ($expr instanceof BooleanAnd || $expr instanceof BinaryOp\LogicalAnd) { - $leftResult = $this->processExprNode($stmt, $expr->left, $scope, $nodeCallback, $context->enterDeep()); - $rightResult = $this->processExprNode($stmt, $expr->right, $leftResult->getTruthyScope(), $nodeCallback, $context); + $leftResult = $this->processExprNode($stmt, $expr->left, $scope, $storage, $nodeCallback, $context->enterDeep()); + $rightResult = $this->processExprNode($stmt, $expr->right, $leftResult->getTruthyScope(), $storage, $nodeCallback, $context); $rightExprType = $rightResult->getScope()->getType($expr->right); if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { $leftMergedWithRightScope = $leftResult->getFalseyScope(); @@ -3470,7 +3547,7 @@ static function (): void { $this->callNodeCallbackWithExpression($nodeCallback, new BooleanAndNode($expr, $leftResult->getTruthyScope()), $scope, $context); - return new ExpressionResult( + $result = new ExpressionResult( $leftMergedWithRightScope, $leftResult->hasYield() || $rightResult->hasYield(), $leftResult->isAlwaysTerminating(), @@ -3479,9 +3556,11 @@ static function (): void { static fn (): MutatingScope => $rightResult->getScope()->filterByTruthyValue($expr), static fn (): MutatingScope => $leftMergedWithRightScope->filterByFalseyValue($expr), ); + $storage->storeResult($expr, $result); + return $result; } elseif ($expr instanceof BooleanOr || $expr instanceof BinaryOp\LogicalOr) { - $leftResult = $this->processExprNode($stmt, $expr->left, $scope, $nodeCallback, $context->enterDeep()); - $rightResult = $this->processExprNode($stmt, $expr->right, $leftResult->getFalseyScope(), $nodeCallback, $context); + $leftResult = $this->processExprNode($stmt, $expr->left, $scope, $storage, $nodeCallback, $context->enterDeep()); + $rightResult = $this->processExprNode($stmt, $expr->right, $leftResult->getFalseyScope(), $storage, $nodeCallback, $context); $rightExprType = $rightResult->getScope()->getType($expr->right); if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { $leftMergedWithRightScope = $leftResult->getTruthyScope(); @@ -3491,7 +3570,7 @@ static function (): void { $this->callNodeCallbackWithExpression($nodeCallback, new BooleanOrNode($expr, $leftResult->getFalseyScope()), $scope, $context); - return new ExpressionResult( + $result = new ExpressionResult( $leftMergedWithRightScope, $leftResult->hasYield() || $rightResult->hasYield(), $leftResult->isAlwaysTerminating(), @@ -3500,15 +3579,17 @@ static function (): void { static fn (): MutatingScope => $leftMergedWithRightScope->filterByTruthyValue($expr), static fn (): MutatingScope => $rightResult->getScope()->filterByFalseyValue($expr), ); + $storage->storeResult($expr, $result); + return $result; } elseif ($expr instanceof Coalesce) { $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->left); $condScope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->left); - $condResult = $this->processExprNode($stmt, $expr->left, $condScope, $nodeCallback, $context->enterDeep()); + $condResult = $this->processExprNode($stmt, $expr->left, $condScope, $storage, $nodeCallback, $context->enterDeep()); $scope = $this->revertNonNullability($condResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); $scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $expr->left); $rightScope = $scope->filterByFalseyValue($expr); - $rightResult = $this->processExprNode($stmt, $expr->right, $rightScope, $nodeCallback, $context->enterDeep()); + $rightResult = $this->processExprNode($stmt, $expr->right, $rightScope, $storage, $nodeCallback, $context->enterDeep()); $rightExprType = $scope->getType($expr->right); if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { $scope = $scope->filterByTruthyValue(new Expr\Isset_([$expr->left])); @@ -3524,7 +3605,7 @@ static function (): void { if ($expr->right instanceof FuncCall && $expr->right->isFirstClassCallable()) { $exprResult = $this->processExprNode($stmt, new FuncCall($expr->right->name, [ new Arg($expr->left, attributes: $expr->getAttribute(ReversePipeTransformerVisitor::ARG_ATTRIBUTES_NAME, [])), - ], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $nodeCallback, $context); + ], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $storage, $nodeCallback, $context); $scope = $exprResult->getScope(); $hasYield = $exprResult->hasYield(); $throwPoints = $exprResult->getThrowPoints(); @@ -3533,7 +3614,7 @@ static function (): void { } elseif ($expr->right instanceof MethodCall && $expr->right->isFirstClassCallable()) { $exprResult = $this->processExprNode($stmt, new MethodCall($expr->right->var, $expr->right->name, [ new Arg($expr->left, attributes: $expr->getAttribute(ReversePipeTransformerVisitor::ARG_ATTRIBUTES_NAME, [])), - ], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $nodeCallback, $context); + ], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $storage, $nodeCallback, $context); $scope = $exprResult->getScope(); $hasYield = $exprResult->hasYield(); $throwPoints = $exprResult->getThrowPoints(); @@ -3542,7 +3623,7 @@ static function (): void { } elseif ($expr->right instanceof StaticCall && $expr->right->isFirstClassCallable()) { $exprResult = $this->processExprNode($stmt, new StaticCall($expr->right->class, $expr->right->name, [ new Arg($expr->left, attributes: $expr->getAttribute(ReversePipeTransformerVisitor::ARG_ATTRIBUTES_NAME, [])), - ], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $nodeCallback, $context); + ], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $storage, $nodeCallback, $context); $scope = $exprResult->getScope(); $hasYield = $exprResult->hasYield(); $throwPoints = $exprResult->getThrowPoints(); @@ -3551,7 +3632,7 @@ static function (): void { } else { $exprResult = $this->processExprNode($stmt, new FuncCall($expr->right, [ new Arg($expr->left, attributes: $expr->getAttribute(ReversePipeTransformerVisitor::ARG_ATTRIBUTES_NAME, [])), - ], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $nodeCallback, $context); + ], array_merge($expr->right->getAttributes(), ['virtualPipeOperatorCall' => true])), $scope, $storage, $nodeCallback, $context); $scope = $exprResult->getScope(); $hasYield = $exprResult->hasYield(); $throwPoints = $exprResult->getThrowPoints(); @@ -3559,13 +3640,13 @@ static function (): void { $isAlwaysTerminating = $exprResult->isAlwaysTerminating(); } } elseif ($expr instanceof BinaryOp) { - $result = $this->processExprNode($stmt, $expr->left, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->left, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $isAlwaysTerminating = $result->isAlwaysTerminating(); - $result = $this->processExprNode($stmt, $expr->right, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->right, $scope, $storage, $nodeCallback, $context->enterDeep()); if ( ($expr instanceof BinaryOp\Div || $expr instanceof BinaryOp\Mod) && !$scope->getType($expr->right)->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no() @@ -3578,7 +3659,7 @@ static function (): void { $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); $isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); } elseif ($expr instanceof Expr\Include_) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = $result->getThrowPoints(); $throwPoints[] = InternalThrowPoint::createImplicit($scope, $expr); $impurePoints = $result->getImpurePoints(); @@ -3593,7 +3674,7 @@ static function (): void { $isAlwaysTerminating = $result->isAlwaysTerminating(); $scope = $result->getScope()->afterExtractCall(); } elseif ($expr instanceof Expr\Print_) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $isAlwaysTerminating = $result->isAlwaysTerminating(); @@ -3602,7 +3683,7 @@ static function (): void { $scope = $result->getScope(); } elseif ($expr instanceof Cast\String_) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $isAlwaysTerminating = $result->isAlwaysTerminating(); @@ -3630,7 +3711,7 @@ static function (): void { || $expr instanceof Expr\UnaryMinus || $expr instanceof Expr\UnaryPlus ) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $isAlwaysTerminating = $result->isAlwaysTerminating(); @@ -3638,7 +3719,7 @@ static function (): void { $scope = $result->getScope(); } elseif ($expr instanceof Expr\Eval_) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = $result->getThrowPoints(); $throwPoints[] = InternalThrowPoint::createImplicit($scope, $expr); $impurePoints = $result->getImpurePoints(); @@ -3648,7 +3729,7 @@ static function (): void { $scope = $result->getScope(); } elseif ($expr instanceof Expr\YieldFrom) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = $result->getThrowPoints(); $throwPoints[] = InternalThrowPoint::createImplicit($scope, $expr); $impurePoints = $result->getImpurePoints(); @@ -3664,7 +3745,7 @@ static function (): void { $scope = $result->getScope(); } elseif ($expr instanceof BooleanNot) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -3674,7 +3755,7 @@ static function (): void { $isAlwaysTerminating = false; if ($expr->class instanceof Expr) { - $result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -3688,7 +3769,7 @@ static function (): void { } if ($expr->name instanceof Expr) { - $result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -3700,7 +3781,7 @@ static function (): void { } elseif ($expr instanceof Expr\Empty_) { $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr); $scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->expr); - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -3717,7 +3798,7 @@ static function (): void { foreach ($expr->vars as $var) { $nonNullabilityResult = $this->ensureNonNullability($scope, $var); $scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $var); - $result = $this->processExprNode($stmt, $var, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $var, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -3732,14 +3813,14 @@ static function (): void { $scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); } } elseif ($expr instanceof Instanceof_) { - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $isAlwaysTerminating = $result->isAlwaysTerminating(); if ($expr->class instanceof Expr) { - $result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -3761,15 +3842,14 @@ static function (): void { if ($expr->class instanceof Expr) { $objectClasses = $scope->getType($expr)->getObjectClassNames(); if (count($objectClasses) === 1) { - $objectExprResult = $this->processExprNode($stmt, new New_(new Name($objectClasses[0])), $scope, static function (): void { - }, $context->enterDeep()); + $objectExprResult = $this->processExprNode($stmt, new New_(new Name($objectClasses[0])), $scope, $storage, new NoopNodeCallback(), $context->enterDeep()); $className = $objectClasses[0]; $additionalThrowPoints = $objectExprResult->getThrowPoints(); } else { $additionalThrowPoints = [InternalThrowPoint::createImplicit($scope, $expr)]; } - $result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -3840,7 +3920,7 @@ static function (): void { if ($constructorReflection->getDeclaringClass()->getName() === $classReflection->getName()) { $constructorResult = null; - $this->processStmtNode($expr->class, $scope, static function (Node $node, Scope $scope) use ($nodeCallback, $classReflection, &$constructorResult): void { + $this->processStmtNode($expr->class, $scope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $classReflection, &$constructorResult): void { $nodeCallback($node, $scope); if (!$node instanceof MethodReturnStatementsNode) { return; @@ -3865,7 +3945,7 @@ static function (): void { $impurePoints = $constructorResult->getImpurePoints(); } } else { - $this->processStmtNode($expr->class, $scope, $nodeCallback, StatementContext::createTopLevel()); + $this->processStmtNode($expr->class, $scope, $storage, $nodeCallback, StatementContext::createTopLevel()); $declaringClass = $constructorReflection->getDeclaringClass(); $constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $parametersAcceptor, $classReflection, $expr, new Name\FullyQualified($declaringClass->getName()), $expr->getArgs(), $scope); if ($constructorThrowPoint !== null) { @@ -3884,11 +3964,11 @@ static function (): void { } } } else { - $this->processStmtNode($expr->class, $scope, $nodeCallback, StatementContext::createTopLevel()); + $this->processStmtNode($expr->class, $scope, $storage, $nodeCallback, StatementContext::createTopLevel()); } } - $result = $this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, $context); + $result = $this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr, $scope, $storage, $nodeCallback, $context); $scope = $result->getScope(); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -3900,7 +3980,7 @@ static function (): void { || $expr instanceof Expr\PreDec || $expr instanceof Expr\PostDec ) { - $result = $this->processExprNode($stmt, $expr->var, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -3916,13 +3996,14 @@ static function (): void { $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $expr->var, $newExpr, $nodeCallback, )->getScope(); } elseif ($expr instanceof Ternary) { - $ternaryCondResult = $this->processExprNode($stmt, $expr->cond, $scope, $nodeCallback, $context->enterDeep()); + $ternaryCondResult = $this->processExprNode($stmt, $expr->cond, $scope, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = $ternaryCondResult->getThrowPoints(); $impurePoints = $ternaryCondResult->getImpurePoints(); $isAlwaysTerminating = $ternaryCondResult->isAlwaysTerminating(); @@ -3930,14 +4011,14 @@ static function (): void { $ifFalseScope = $ternaryCondResult->getFalseyScope(); $ifTrueType = null; if ($expr->if !== null) { - $ifResult = $this->processExprNode($stmt, $expr->if, $ifTrueScope, $nodeCallback, $context); + $ifResult = $this->processExprNode($stmt, $expr->if, $ifTrueScope, $storage, $nodeCallback, $context); $throwPoints = array_merge($throwPoints, $ifResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $ifResult->getImpurePoints()); $ifTrueScope = $ifResult->getScope(); $ifTrueType = $ifTrueScope->getType($expr->if); } - $elseResult = $this->processExprNode($stmt, $expr->else, $ifFalseScope, $nodeCallback, $context); + $elseResult = $this->processExprNode($stmt, $expr->else, $ifFalseScope, $storage, $nodeCallback, $context); $throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $elseResult->getImpurePoints()); $ifFalseScope = $elseResult->getScope(); @@ -3961,7 +4042,7 @@ static function (): void { } } - return new ExpressionResult( + $result = new ExpressionResult( $finalScope, $ternaryCondResult->hasYield(), $isAlwaysTerminating, @@ -3970,6 +4051,8 @@ static function (): void { static fn (): MutatingScope => $finalScope->filterByTruthyValue($expr), static fn (): MutatingScope => $finalScope->filterByFalseyValue($expr), ); + $storage->storeResult($expr, $result); + return $result; } elseif ($expr instanceof Expr\Yield_) { $throwPoints = [ @@ -3986,14 +4069,14 @@ static function (): void { ]; $isAlwaysTerminating = false; if ($expr->key !== null) { - $keyResult = $this->processExprNode($stmt, $expr->key, $scope, $nodeCallback, $context->enterDeep()); + $keyResult = $this->processExprNode($stmt, $expr->key, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $keyResult->getScope(); $throwPoints = $keyResult->getThrowPoints(); $impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints()); $isAlwaysTerminating = $keyResult->isAlwaysTerminating(); } if ($expr->value !== null) { - $valueResult = $this->processExprNode($stmt, $expr->value, $scope, $nodeCallback, $context->enterDeep()); + $valueResult = $this->processExprNode($stmt, $expr->value, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $valueResult->getScope(); $throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); @@ -4003,7 +4086,7 @@ static function (): void { } elseif ($expr instanceof Expr\Match_) { $deepContext = $context->enterDeep(); $condType = $scope->getType($expr->cond); - $condResult = $this->processExprNode($stmt, $expr->cond, $scope, $nodeCallback, $deepContext); + $condResult = $this->processExprNode($stmt, $expr->cond, $scope, $storage, $nodeCallback, $deepContext); $scope = $condResult->getScope(); $hasYield = $condResult->hasYield(); $throwPoints = $condResult->getThrowPoints(); @@ -4014,6 +4097,7 @@ static function (): void { $hasDefaultCond = false; $hasAlwaysTrueCond = false; $arms = $expr->arms; + $armCondsToSkip = []; if ($condType->isEnum()->yes()) { // enum match analysis would work even without this if branch // but would be much slower @@ -4034,7 +4118,7 @@ static function (): void { $condNodes = []; $conditionCases = []; $conditionExprs = []; - foreach ($arm->conds as $cond) { + foreach ($arm->conds as $j => $cond) { if (!$cond instanceof Expr\ClassConstFetch) { continue 2; } @@ -4084,7 +4168,7 @@ static function (): void { } } - $this->processExprNode($stmt, $cond, $armConditionScope, $nodeCallback, $deepContext); + $this->processExprNode($stmt, $cond, $armConditionScope, $storage, $nodeCallback, $deepContext); $condNodes[] = new MatchExpressionArmCondition( $cond, @@ -4094,6 +4178,7 @@ static function (): void { $conditionExprs[] = $cond; unset($unusedIndexedEnumCases[$loweredFetchedClassName][$caseName]); + $armCondsToSkip[$i][$j] = true; } $conditionCasesCount = count($conditionCases); @@ -4117,6 +4202,7 @@ static function (): void { $stmt, $arm->body, $matchArmBodyScope, + $storage, $nodeCallback, ExpressionContext::createTopLevel(), ); @@ -4153,7 +4239,7 @@ static function (): void { $hasDefaultCond = true; $matchArmBody = new MatchExpressionArmBody($matchScope, $arm->body); $armNodes[$i] = new MatchExpressionArm($matchArmBody, [], $arm->getStartLine()); - $armResult = $this->processExprNode($stmt, $arm->body, $matchScope, $nodeCallback, ExpressionContext::createTopLevel()); + $armResult = $this->processExprNode($stmt, $arm->body, $matchScope, $storage, $nodeCallback, ExpressionContext::createTopLevel()); $matchScope = $armResult->getScope(); $hasYield = $hasYield || $armResult->hasYield(); $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); @@ -4169,9 +4255,12 @@ static function (): void { $filteringExprs = []; $armCondScope = $matchScope; $condNodes = []; - foreach ($arm->conds as $armCond) { + foreach ($arm->conds as $j => $armCond) { + if (isset($armCondsToSkip[$i][$j])) { + continue; + } $condNodes[] = new MatchExpressionArmCondition($armCond, $armCondScope, $armCond->getStartLine()); - $armCondResult = $this->processExprNode($stmt, $armCond, $armCondScope, $nodeCallback, $deepContext); + $armCondResult = $this->processExprNode($stmt, $armCond, $armCondScope, $storage, $nodeCallback, $deepContext); $hasYield = $hasYield || $armCondResult->hasYield(); $throwPoints = array_merge($throwPoints, $armCondResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $armCondResult->getImpurePoints()); @@ -4186,9 +4275,7 @@ static function (): void { } $filteringExpr = $this->getFilteringExprForMatchArm($expr, $filteringExprs); - - $bodyScope = $this->processExprNode($stmt, $filteringExpr, $matchScope, static function (): void { - }, $deepContext)->getTruthyScope(); + $bodyScope = $matchScope->filterByTruthyValue($filteringExpr); $matchArmBody = new MatchExpressionArmBody($bodyScope, $arm->body); $armNodes[$i] = new MatchExpressionArm($matchArmBody, $condNodes, $arm->getStartLine()); @@ -4196,6 +4283,7 @@ static function (): void { $stmt, $arm->body, $bodyScope, + $storage, $nodeCallback, ExpressionContext::createTopLevel(), ); @@ -4216,7 +4304,7 @@ static function (): void { $nodeCallback(new MatchExpressionNode($expr->cond, array_values($armNodes), $expr, $matchScope), $scope); } elseif ($expr instanceof AlwaysRememberedExpr) { - $result = $this->processExprNode($stmt, $expr->getExpr(), $scope, $nodeCallback, $context); + $result = $this->processExprNode($stmt, $expr->getExpr(), $scope, $storage, $nodeCallback, $context); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); @@ -4224,7 +4312,7 @@ static function (): void { $scope = $result->getScope(); } elseif ($expr instanceof Expr\Throw_) { $hasYield = false; - $result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $isAlwaysTerminating = $result->isAlwaysTerminating(); @@ -4235,7 +4323,7 @@ static function (): void { $hasYield = false; $isAlwaysTerminating = false; if ($expr->getName() instanceof Expr) { - $result = $this->processExprNode($stmt, $expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $result = $this->processExprNode($stmt, $expr->getName(), $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -4243,14 +4331,14 @@ static function (): void { $isAlwaysTerminating = $result->isAlwaysTerminating(); } } elseif ($expr instanceof MethodCallableNode) { - $result = $this->processExprNode($stmt, $expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $result = $this->processExprNode($stmt, $expr->getVar(), $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $isAlwaysTerminating = false; if ($expr->getName() instanceof Expr) { - $nameResult = $this->processExprNode($stmt, $expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $nameResult = $this->processExprNode($stmt, $expr->getName(), $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $nameResult->getScope(); $hasYield = $hasYield || $nameResult->hasYield(); $throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); @@ -4263,7 +4351,7 @@ static function (): void { $hasYield = false; $isAlwaysTerminating = false; if ($expr->getClass() instanceof Expr) { - $classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $classResult->getScope(); $hasYield = $classResult->hasYield(); $throwPoints = $classResult->getThrowPoints(); @@ -4271,7 +4359,7 @@ static function (): void { $isAlwaysTerminating = $classResult->isAlwaysTerminating(); } if ($expr->getName() instanceof Expr) { - $nameResult = $this->processExprNode($stmt, $expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $nameResult = $this->processExprNode($stmt, $expr->getName(), $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $nameResult->getScope(); $hasYield = $hasYield || $nameResult->hasYield(); $throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); @@ -4284,7 +4372,7 @@ static function (): void { $hasYield = false; $isAlwaysTerminating = false; if ($expr->getClass() instanceof Expr) { - $classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $classResult->getScope(); $hasYield = $classResult->hasYield(); $throwPoints = $classResult->getThrowPoints(); @@ -4296,6 +4384,8 @@ static function (): void { $throwPoints = []; $impurePoints = []; $isAlwaysTerminating = false; + $expressionType = $this->initializerExprTypeResolver->getType($expr, InitializerExprContext::fromScope($scope)); + $expressionNativeType = $expressionType; } elseif ($expr instanceof ConstFetch) { $hasYield = false; $throwPoints = []; @@ -4309,7 +4399,7 @@ static function (): void { $isAlwaysTerminating = false; } - return new ExpressionResult( + $result = new ExpressionResult( $scope, $hasYield, $isAlwaysTerminating, @@ -4317,7 +4407,11 @@ static function (): void { $impurePoints, static fn (): MutatingScope => $scope->filterByTruthyValue($expr), static fn (): MutatingScope => $scope->filterByFalseyValue($expr), + $expressionType, + $expressionNativeType, ); + $storage->storeResult($expr, $result); + return $result; } private function getArrayFunctionAppendingType(FunctionReflection $functionReflection, Scope $scope, FuncCall $expr): Type @@ -4793,13 +4887,14 @@ private function processClosureNode( Node\Stmt $stmt, Expr\Closure $expr, MutatingScope $scope, + ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context, ?Type $passedToType, ): ProcessClosureResult { foreach ($expr->params as $param) { - $this->processParamNode($stmt, $param, $scope, $nodeCallback); + $this->processParamNode($stmt, $param, $scope, $storage, $nodeCallback); } $byRefUses = []; @@ -4849,7 +4944,7 @@ private function processClosureNode( $scope = $scope->assignVariable($inAssignRightSideVariableName, $variableType, $variableNativeType, TrinaryLogic::createYes()); } } - $this->processExprNode($stmt, $use->var, $useScope, $nodeCallback, $context); + $this->processExprNode($stmt, $use->var, $useScope, $storage, $nodeCallback, $context); if (!$use->byRef) { continue; } @@ -4912,7 +5007,7 @@ private function processClosureNode( }; if (count($byRefUses) === 0) { - $statementResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel()); + $statementResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $closureStmtsCallback, StatementContext::createTopLevel()); $publicStatementResult = $statementResult->toPublic(); $nodeCallback(new ClosureReturnStatementsNode( $expr, @@ -4926,13 +5021,15 @@ private function processClosureNode( return new ProcessClosureResult($scope, $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions, $isAlwaysTerminating); } + $originalStorage = $storage; + $count = 0; $closureResultScope = null; do { $prevScope = $closureScope; - $intermediaryClosureScopeResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, static function (): void { - }, StatementContext::createTopLevel()); + $storage = $originalStorage->duplicate(); + $intermediaryClosureScopeResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, new NoopNodeCallback(), StatementContext::createTopLevel()); $intermediaryClosureScope = $intermediaryClosureScopeResult->getScope(); foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) { $intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope()); @@ -4959,7 +5056,8 @@ private function processClosureNode( $closureResultScope = $closureScope; } - $statementResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel()); + $storage = $originalStorage; + $statementResult = $this->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $closureStmtsCallback, StatementContext::createTopLevel()); $publicStatementResult = $statementResult->toPublic(); $nodeCallback(new ClosureReturnStatementsNode( $expr, @@ -5013,12 +5111,13 @@ private function processArrowFunctionNode( Node\Stmt $stmt, Expr\ArrowFunction $expr, MutatingScope $scope, + ExpressionResultStorage $storage, callable $nodeCallback, ?Type $passedToType, ): ExpressionResult { foreach ($expr->params as $param) { - $this->processParamNode($stmt, $param, $scope, $nodeCallback); + $this->processParamNode($stmt, $param, $scope, $storage, $nodeCallback); } if ($expr->returnType !== null) { $nodeCallback($expr->returnType, $scope); @@ -5036,9 +5135,12 @@ private function processArrowFunctionNode( throw new ShouldNotHappenException(); } $nodeCallback(new InArrowFunctionNode($arrowFunctionType, $expr), $arrowFunctionScope); - $exprResult = $this->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $nodeCallback, ExpressionContext::createTopLevel()); + $exprResult = $this->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $storage, $nodeCallback, ExpressionContext::createTopLevel()); + + $result = new ExpressionResult($scope, false, $exprResult->isAlwaysTerminating(), $exprResult->getThrowPoints(), $exprResult->getImpurePoints()); + $storage->storeResult($expr, $result); - return new ExpressionResult($scope, false, $exprResult->isAlwaysTerminating(), $exprResult->getThrowPoints(), $exprResult->getImpurePoints()); + return $result; } /** @@ -5131,10 +5233,11 @@ private function processParamNode( Node\Stmt $stmt, Node\Param $param, MutatingScope $scope, + ExpressionResultStorage $storage, callable $nodeCallback, ): void { - $this->processAttributeGroups($stmt, $param->attrGroups, $scope, $nodeCallback); + $this->processAttributeGroups($stmt, $param->attrGroups, $scope, $storage, $nodeCallback); $nodeCallback($param, $scope); if ($param->type !== null) { $nodeCallback($param->type, $scope); @@ -5143,7 +5246,7 @@ private function processParamNode( return; } - $this->processExprNode($stmt, $param->default, $scope, $nodeCallback, ExpressionContext::createDeep()); + $this->processExprNode($stmt, $param->default, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); } /** @@ -5154,6 +5257,7 @@ private function processAttributeGroups( Node\Stmt $stmt, array $attrGroups, MutatingScope $scope, + ExpressionResultStorage $storage, callable $nodeCallback, ): void { @@ -5172,14 +5276,14 @@ private function processAttributeGroups( ); $expr = new New_($attr->name, $attr->args); $expr = ArgumentsNormalizer::reorderNewArguments($parametersAcceptor, $expr) ?? $expr; - $this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr, $scope, $nodeCallback, ExpressionContext::createDeep()); + $this->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $nodeCallback($attr, $scope); continue; } } foreach ($attr->args as $arg) { - $this->processExprNode($stmt, $arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); + $this->processExprNode($stmt, $arg->value, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $nodeCallback($arg, $scope); } $nodeCallback($attr, $scope); @@ -5199,6 +5303,7 @@ private function processPropertyHooks( string $propertyName, array $hooks, MutatingScope $scope, + ExpressionResultStorage $storage, callable $nodeCallback, ): void { @@ -5210,12 +5315,12 @@ private function processPropertyHooks( foreach ($hooks as $hook) { $nodeCallback($hook, $scope); - $this->processAttributeGroups($stmt, $hook->attrGroups, $scope, $nodeCallback); + $this->processAttributeGroups($stmt, $hook->attrGroups, $scope, $storage, $nodeCallback); [, $phpDocParameterTypes,,,, $phpDocThrowType,,,,,,,, $phpDocComment] = $this->getPhpDocs($scope, $hook); foreach ($hook->params as $param) { - $this->processParamNode($stmt, $param, $scope, $nodeCallback); + $this->processParamNode($stmt, $param, $scope, $storage, $nodeCallback); } [$isDeprecated, $deprecatedDescription] = $this->getDeprecatedAttribute($scope, $hook); @@ -5265,7 +5370,7 @@ private function processPropertyHooks( $gatheredReturnStatements = []; $executionEnds = []; $methodImpurePoints = []; - $statementResult = $this->processStmtNodesInternal(new PropertyHookStatementNode($hook), $stmts, $hookScope, static function (Node $node, Scope $scope) use ($nodeCallback, $hookScope, &$gatheredReturnStatements, &$executionEnds, &$hookImpurePoints): void { + $statementResult = $this->processStmtNodesInternal(new PropertyHookStatementNode($hook), $stmts, $hookScope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $hookScope, &$gatheredReturnStatements, &$executionEnds, &$hookImpurePoints): void { $nodeCallback($node, $scope); if ($scope->getFunction() !== $hookScope->getFunction()) { return; @@ -5367,6 +5472,7 @@ private function processArgs( ?ParametersAcceptor $parametersAcceptor, CallLike $callLike, MutatingScope $scope, + ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context, ?MutatingScope $closureBindScope = null, @@ -5477,7 +5583,7 @@ private function processArgs( } $this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context); - $closureResult = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context, $parameterType ?? null); + $closureResult = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $context, $parameterType ?? null); if ($callCallbackImmediately) { $throwPoints = array_merge($throwPoints, array_map(static fn (InternalThrowPoint $throwPoint) => $throwPoint->isExplicit() ? InternalThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : InternalThrowPoint::createImplicit($scope, $arg->value), $closureResult->getThrowPoints())); $impurePoints = array_merge($impurePoints, $closureResult->getImpurePoints()); @@ -5532,7 +5638,7 @@ private function processArgs( } $this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context); - $arrowFunctionResult = $this->processArrowFunctionNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $parameterType ?? null); + $arrowFunctionResult = $this->processArrowFunctionNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $parameterType ?? null); if ($callCallbackImmediately) { $throwPoints = array_merge($throwPoints, array_map(static fn (InternalThrowPoint $throwPoint) => $throwPoint->isExplicit() ? InternalThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : InternalThrowPoint::createImplicit($scope, $arg->value), $arrowFunctionResult->getThrowPoints())); $impurePoints = array_merge($impurePoints, $arrowFunctionResult->getImpurePoints()); @@ -5540,7 +5646,7 @@ private function processArgs( } } else { $exprType = $scope->getType($arg->value); - $exprResult = $this->processExprNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context->enterDeep()); + $exprResult = $this->processExprNode($stmt, $arg->value, $scopeToPass, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $exprResult->getImpurePoints()); $isAlwaysTerminating = $isAlwaysTerminating || $exprResult->isAlwaysTerminating(); @@ -5626,11 +5732,13 @@ private function processArgs( $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $argValue, new TypeExpr($byRefType), $nodeCallback, )->getScope(); + $scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $argValue); } } elseif ($calleeReflection !== null && $calleeReflection->hasSideEffects()->yes()) { $argType = $scope->getType($arg->value); @@ -5660,6 +5768,7 @@ private function processArgs( } } + // not storing this, it's scope after processing all args return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } @@ -5754,6 +5863,7 @@ private function getParameterOutExtensionsType(CallLike $callLike, $calleeReflec */ private function processAssignVar( MutatingScope $scope, + ExpressionResultStorage $storage, Node\Stmt $stmt, Expr $var, Expr $assignedExpr, @@ -5769,12 +5879,16 @@ private function processAssignVar( $impurePoints = []; $isAlwaysTerminating = false; $isAssignOp = $assignedExpr instanceof Expr\AssignOp && !$enterExpressionAssign; + $expressionType = null; + $expressionNativeType = null; if ($var instanceof Variable && is_string($var->name)) { $result = $processExprCallback($scope); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); $isAlwaysTerminating = $result->isAlwaysTerminating(); + $expressionType = $result->getType(); + $expressionNativeType = $result->getNativeType(); if (in_array($var->name, Scope::SUPERGLOBAL_VARIABLES, true)) { $impurePoints[] = new ImpurePoint($scope, $var, 'superglobal', 'assign to superglobal variable', true); } @@ -5787,8 +5901,7 @@ private function processAssignVar( if ($if === null) { $if = $assignedExpr->cond; } - $condScope = $this->processExprNode($stmt, $assignedExpr->cond, $scope, static function (): void { - }, ExpressionContext::createDeep())->getScope(); + $condScope = $this->processExprNode($stmt, $assignedExpr->cond, $scope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep())->getScope(); $truthySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($condScope, $assignedExpr->cond, TypeSpecifierContext::createTruthy()); $falseySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($condScope, $assignedExpr->cond, TypeSpecifierContext::createFalsey()); $truthyScope = $condScope->filterBySpecifiedTypes($truthySpecifiedTypes); @@ -5860,7 +5973,7 @@ private function processAssignVar( if ($enterExpressionAssign) { $scope = $scope->enterExpressionAssign($var); } - $result = $this->processExprNode($stmt, $var, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $var, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); @@ -5894,7 +6007,7 @@ private function processAssignVar( if ($enterExpressionAssign) { $scope->enterExpressionAssign($dimExpr); } - $result = $this->processExprNode($stmt, $dimExpr, $scope, $nodeCallback, $context->enterDeep()); + $result = $this->processExprNode($stmt, $dimExpr, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $scope = $result->getScope(); @@ -6004,13 +6117,13 @@ private function processAssignVar( $stmt, new MethodCall($var, 'offsetSet'), $scope, - static function (): void { - }, + $storage, + new NoopNodeCallback(), $context, )->getThrowPoints()); } } elseif ($var instanceof PropertyFetch) { - $objectResult = $this->processExprNode($stmt, $var->var, $scope, $nodeCallback, $context); + $objectResult = $this->processExprNode($stmt, $var->var, $scope, $storage, $nodeCallback, $context); $hasYield = $objectResult->hasYield(); $throwPoints = $objectResult->getThrowPoints(); $impurePoints = $objectResult->getImpurePoints(); @@ -6021,7 +6134,7 @@ static function (): void { if ($var->name instanceof Node\Identifier) { $propertyName = $var->name->name; } else { - $propertyNameResult = $this->processExprNode($stmt, $var->name, $scope, $nodeCallback, $context); + $propertyNameResult = $this->processExprNode($stmt, $var->name, $scope, $storage, $nodeCallback, $context); $hasYield = $hasYield || $propertyNameResult->hasYield(); $throwPoints = array_merge($throwPoints, $propertyNameResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $propertyNameResult->getImpurePoints()); @@ -6099,8 +6212,8 @@ static function (): void { $stmt, new MethodCall($var->var, '__set'), $scope, - static function (): void { - }, + $storage, + new NoopNodeCallback(), $context, )->getThrowPoints()); } @@ -6110,7 +6223,7 @@ static function (): void { if ($var->class instanceof Node\Name) { $propertyHolderType = $scope->resolveTypeByName($var->class); } else { - $this->processExprNode($stmt, $var->class, $scope, $nodeCallback, $context); + $this->processExprNode($stmt, $var->class, $scope, $storage, $nodeCallback, $context); $propertyHolderType = $scope->getType($var->class); } @@ -6118,7 +6231,7 @@ static function (): void { if ($var->name instanceof Node\Identifier) { $propertyName = $var->name->name; } else { - $propertyNameResult = $this->processExprNode($stmt, $var->name, $scope, $nodeCallback, $context); + $propertyNameResult = $this->processExprNode($stmt, $var->name, $scope, $storage, $nodeCallback, $context); $hasYield = $propertyNameResult->hasYield(); $throwPoints = $propertyNameResult->getThrowPoints(); $impurePoints = $propertyNameResult->getImpurePoints(); @@ -6190,7 +6303,7 @@ static function (): void { $itemScope = $this->lookForSetAllowedUndefinedExpressions($itemScope, $arrayItem->value); $nodeCallback($arrayItem, $itemScope); if ($arrayItem->key !== null) { - $keyResult = $this->processExprNode($stmt, $arrayItem->key, $itemScope, $nodeCallback, $context->enterDeep()); + $keyResult = $this->processExprNode($stmt, $arrayItem->key, $itemScope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $keyResult->hasYield(); $throwPoints = array_merge($throwPoints, $keyResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints()); @@ -6205,6 +6318,7 @@ static function (): void { } $result = $this->processAssignVar( $scope, + $storage, $stmt, $arrayItem->value, new GetOffsetValueTypeExpr($assignedExpr, $dimExpr), @@ -6295,26 +6409,22 @@ static function (): void { $scope = $result->getScope(); } - return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + // stored where processAssignVar is called + return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints, type: $expressionType, nativeType: $expressionNativeType); } /** * @param callable(Node $node, Scope $scope): void $nodeCallback */ - private function processVirtualAssign(MutatingScope $scope, Node\Stmt $stmt, Expr $var, Expr $assignedExpr, callable $nodeCallback): ExpressionResult + private function processVirtualAssign(MutatingScope $scope, ExpressionResultStorage $storage, Node\Stmt $stmt, Expr $var, Expr $assignedExpr, callable $nodeCallback): ExpressionResult { return $this->processAssignVar( $scope, + $storage, $stmt, $var, $assignedExpr, - static function (Node $node, Scope $scope) use ($nodeCallback): void { - if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) { - return; - } - - $nodeCallback($node, $scope); - }, + new VirtualAssignNodeCallback($nodeCallback), ExpressionContext::createDeep(), static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, false, [], []), false, @@ -6677,7 +6787,7 @@ private function processVarAnnotation(MutatingScope $scope, array $variableNames /** * @param callable(Node $node, Scope $scope): void $nodeCallback */ - private function enterForeach(MutatingScope $scope, MutatingScope $originalScope, Foreach_ $stmt, callable $nodeCallback): MutatingScope + private function enterForeach(MutatingScope $scope, ExpressionResultStorage $storage, MutatingScope $originalScope, Foreach_ $stmt, callable $nodeCallback): MutatingScope { if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { $scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt); @@ -6706,6 +6816,7 @@ private function enterForeach(MutatingScope $scope, MutatingScope $originalScope } else { $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $stmt->valueVar, new GetIterableValueTypeExpr($stmt->expr), @@ -6720,6 +6831,7 @@ private function enterForeach(MutatingScope $scope, MutatingScope $originalScope } elseif ($stmt->keyVar !== null) { $scope = $this->processVirtualAssign( $scope, + $storage, $stmt, $stmt->keyVar, new GetIterableKeyTypeExpr($stmt->expr), @@ -6786,7 +6898,7 @@ private function enterForeach(MutatingScope $scope, MutatingScope $originalScope /** * @param callable(Node $node, Scope $scope): void $nodeCallback */ - private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classScope, callable $nodeCallback): void + private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classScope, ExpressionResultStorage $storage, callable $nodeCallback): void { $parentTraitNames = []; $parent = $classScope->getParentScope(); @@ -6827,7 +6939,7 @@ private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classS $adaptations[] = $adaptation; } $parserNodes = $this->parser->parseFile($fileName); - $this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $adaptations, $nodeCallback); + $this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $storage, $adaptations, $nodeCallback); } } @@ -6836,7 +6948,7 @@ private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classS * @param Node\Stmt\TraitUseAdaptation[] $adaptations * @param callable(Node $node, Scope $scope): void $nodeCallback */ - private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, array $adaptations, callable $nodeCallback): void + private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, ExpressionResultStorage $storage, array $adaptations, callable $nodeCallback): void { if ($node instanceof Node) { if ($node instanceof Node\Stmt\Trait_ && $traitReflection->getName() === (string) $node->namespacedName && $traitReflection->getNativeReflection()->getStartLine() === $node->getStartLine()) { @@ -6884,7 +6996,7 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection } $traitScope = $scope->enterTrait($traitReflection); $nodeCallback(new InTraitNode($node, $traitReflection, $scope->getClassReflection()), $traitScope); - $this->processStmtNodesInternal($node, $stmts, $traitScope, $nodeCallback, StatementContext::createTopLevel()); + $this->processStmtNodesInternal($node, $stmts, $traitScope, $storage, $nodeCallback, StatementContext::createTopLevel()); return; } if ($node instanceof Node\Stmt\ClassLike) { @@ -6895,11 +7007,11 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection } foreach ($node->getSubNodeNames() as $subNodeName) { $subNode = $node->{$subNodeName}; - $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); + $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $storage, $adaptations, $nodeCallback); } } elseif (is_array($node)) { foreach ($node as $subNode) { - $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); + $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $storage, $adaptations, $nodeCallback); } } } @@ -6936,7 +7048,7 @@ private function processCalledMethod(MethodReflection $methodReflection): ?Mutat $parserNodes = $this->parser->parseFile($fileName); $returnStatement = null; - $this->processNodesForCalledMethod($parserNodes, $fileName, $methodReflection, static function (Node $node, Scope $scope) use ($methodReflection, &$returnStatement): void { + $this->processNodesForCalledMethod($parserNodes, new ExpressionResultStorage(), $fileName, $methodReflection, static function (Node $node, Scope $scope) use ($methodReflection, &$returnStatement): void { if (!$node instanceof MethodReturnStatementsNode) { return; } @@ -6991,7 +7103,7 @@ private function processCalledMethod(MethodReflection $methodReflection): ?Mutat * @param Node[]|Node|scalar|null $node * @param callable(Node $node, Scope $scope): void $nodeCallback */ - private function processNodesForCalledMethod($node, string $fileName, MethodReflection $methodReflection, callable $nodeCallback): void + private function processNodesForCalledMethod($node, ExpressionResultStorage $storage, string $fileName, MethodReflection $methodReflection, callable $nodeCallback): void { if ($node instanceof Node) { $declaringClass = $methodReflection->getDeclaringClass(); @@ -7017,7 +7129,7 @@ private function processNodesForCalledMethod($node, string $fileName, MethodRefl } $scope = $this->scopeFactory->create(ScopeContext::create($fileName))->enterClass($declaringClass); - $this->processStmtNode($stmt, $scope, $nodeCallback, StatementContext::createTopLevel()); + $this->processStmtNode($stmt, $scope, $storage, $nodeCallback, StatementContext::createTopLevel()); } return; } @@ -7029,11 +7141,11 @@ private function processNodesForCalledMethod($node, string $fileName, MethodRefl } foreach ($node->getSubNodeNames() as $subNodeName) { $subNode = $node->{$subNodeName}; - $this->processNodesForCalledMethod($subNode, $fileName, $methodReflection, $nodeCallback); + $this->processNodesForCalledMethod($subNode, $storage, $fileName, $methodReflection, $nodeCallback); } } elseif (is_array($node)) { foreach ($node as $subNode) { - $this->processNodesForCalledMethod($subNode, $fileName, $methodReflection, $nodeCallback); + $this->processNodesForCalledMethod($subNode, $storage, $fileName, $methodReflection, $nodeCallback); } } } diff --git a/src/Analyser/NoopNodeCallback.php b/src/Analyser/NoopNodeCallback.php new file mode 100644 index 0000000000..61bc078958 --- /dev/null +++ b/src/Analyser/NoopNodeCallback.php @@ -0,0 +1,15 @@ +originalNodeCallback)($node, $scope); + } + +} diff --git a/tests/PHPStan/Analyser/ExpressionResultTest.php b/tests/PHPStan/Analyser/ExpressionResultTest.php index 56b699f1fc..38b8b5d368 100644 --- a/tests/PHPStan/Analyser/ExpressionResultTest.php +++ b/tests/PHPStan/Analyser/ExpressionResultTest.php @@ -208,6 +208,7 @@ public function testIsAlwaysTerminating( $stmt, $expr, $scope, + new ExpressionResultStorage(), static function (): void { }, ExpressionContext::createTopLevel(), diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index 014b57899c..5f081aee3d 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -148,10 +148,6 @@ public function testEnums(): void 'Match arm comparison between MatchEnums\Foo and MatchEnums\Foo::ONE is always false.', 104, ], - [ - 'Match arm comparison between *NEVER* and MatchEnums\Foo::ONE is always false.', - 113, - ], [ 'Match arm comparison between *NEVER* and MatchEnums\DifferentEnum::ONE is always false.', 113,