From 476839af66eb0298f94fd7bd7aca0c7e6ec31d95 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Jun 2026 10:59:27 +0200 Subject: [PATCH 01/22] Introduce ExpressionResultFactory --- .../ExprHandler/ArrayDimFetchHandler.php | 9 +++++-- src/Analyser/ExprHandler/ArrayHandler.php | 4 +++- .../ExprHandler/ArrowFunctionHandler.php | 4 +++- src/Analyser/ExprHandler/AssignHandler.php | 12 ++++++---- src/Analyser/ExprHandler/AssignOpHandler.php | 8 ++++--- src/Analyser/ExprHandler/BinaryOpHandler.php | 4 +++- .../ExprHandler/BitwiseNotHandler.php | 4 +++- .../ExprHandler/BooleanAndHandler.php | 4 +++- .../ExprHandler/BooleanNotHandler.php | 7 +++++- src/Analyser/ExprHandler/BooleanOrHandler.php | 4 +++- src/Analyser/ExprHandler/CastHandler.php | 4 +++- .../ExprHandler/CastStringHandler.php | 4 +++- .../ExprHandler/ClassConstFetchHandler.php | 4 +++- src/Analyser/ExprHandler/CloneHandler.php | 7 +++++- src/Analyser/ExprHandler/ClosureHandler.php | 4 +++- src/Analyser/ExprHandler/CoalesceHandler.php | 4 +++- .../ExprHandler/ConstFetchHandler.php | 4 +++- src/Analyser/ExprHandler/EmptyHandler.php | 4 +++- .../ExprHandler/ErrorSuppressHandler.php | 7 +++++- src/Analyser/ExprHandler/EvalHandler.php | 7 +++++- src/Analyser/ExprHandler/ExitHandler.php | 7 +++++- src/Analyser/ExprHandler/FuncCallHandler.php | 4 +++- .../Helper/ImplicitToStringCallHelper.php | 6 +++-- src/Analyser/ExprHandler/IncludeHandler.php | 7 +++++- .../ExprHandler/InstanceofHandler.php | 7 +++++- .../ExprHandler/InterpolatedStringHandler.php | 4 +++- src/Analyser/ExprHandler/IssetHandler.php | 4 +++- src/Analyser/ExprHandler/MatchHandler.php | 4 +++- .../ExprHandler/MethodCallHandler.php | 6 +++-- src/Analyser/ExprHandler/NewHandler.php | 4 +++- .../ExprHandler/NullsafeMethodCallHandler.php | 4 +++- .../NullsafePropertyFetchHandler.php | 4 +++- src/Analyser/ExprHandler/PipeHandler.php | 7 +++++- src/Analyser/ExprHandler/PostDecHandler.php | 7 +++++- src/Analyser/ExprHandler/PostIncHandler.php | 7 +++++- src/Analyser/ExprHandler/PreDecHandler.php | 7 +++++- src/Analyser/ExprHandler/PreIncHandler.php | 7 +++++- src/Analyser/ExprHandler/PrintHandler.php | 4 +++- .../ExprHandler/PropertyFetchHandler.php | 4 +++- src/Analyser/ExprHandler/ScalarHandler.php | 4 +++- .../ExprHandler/StaticCallHandler.php | 4 +++- .../StaticPropertyFetchHandler.php | 4 +++- src/Analyser/ExprHandler/TernaryHandler.php | 4 +++- src/Analyser/ExprHandler/ThrowHandler.php | 7 +++++- .../ExprHandler/UnaryMinusHandler.php | 4 +++- src/Analyser/ExprHandler/UnaryPlusHandler.php | 4 +++- src/Analyser/ExprHandler/VariableHandler.php | 7 +++++- .../Virtual/AlwaysRememberedExprHandler.php | 7 +++++- .../Virtual/ExistingArrayDimFetchHandler.php | 7 +++++- .../Virtual/FunctionCallableNodeHandler.php | 7 +++++- .../Virtual/GetIterableKeyTypeExprHandler.php | 7 +++++- .../GetIterableValueTypeExprHandler.php | 7 +++++- .../Virtual/GetOffsetValueTypeExprHandler.php | 7 +++++- .../InstantiationCallableNodeHandler.php | 7 +++++- .../Virtual/MethodCallableNodeHandler.php | 7 +++++- .../Virtual/NativeTypeExprHandler.php | 7 +++++- .../OriginalPropertyTypeExprHandler.php | 4 +++- .../SetExistingOffsetValueTypeExprHandler.php | 7 +++++- .../Virtual/SetOffsetValueTypeExprHandler.php | 7 +++++- .../StaticMethodCallableNodeHandler.php | 7 +++++- .../ExprHandler/Virtual/TypeExprHandler.php | 7 +++++- .../Virtual/UnsetOffsetExprHandler.php | 7 +++++- src/Analyser/ExprHandler/YieldFromHandler.php | 7 +++++- src/Analyser/ExprHandler/YieldHandler.php | 7 +++++- src/Analyser/ExpressionResult.php | 3 +++ src/Analyser/ExpressionResultFactory.php | 24 +++++++++++++++++++ src/Analyser/NodeScopeResolver.php | 11 +++++---- src/Testing/RuleTestCase.php | 2 ++ src/Testing/TypeInferenceTestCase.php | 2 ++ tests/PHPStan/Analyser/AnalyserTest.php | 1 + .../Fiber/FiberNodeScopeResolverRuleTest.php | 2 ++ .../Fiber/FiberNodeScopeResolverTest.php | 2 ++ 72 files changed, 336 insertions(+), 78 deletions(-) create mode 100644 src/Analyser/ExpressionResultFactory.php diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index 1c389f9fb9a..f82489c876b 100644 --- a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php @@ -11,6 +11,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper; @@ -35,6 +36,10 @@ final class ArrayDimFetchHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof ArrayDimFetch; @@ -80,7 +85,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $varResult->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), @@ -109,7 +114,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex )->getThrowPoints()); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $dimResult->hasYield() || $varResult->hasYield(), isAlwaysTerminating: $dimResult->isAlwaysTerminating() || $varResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index f64a168e848..580ab998b6c 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -10,6 +10,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -37,6 +38,7 @@ final class ArrayHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -98,7 +100,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } $nodeScopeResolver->callNodeCallback($nodeCallback, new LiteralArrayNode($expr, $itemNodes), $scope, $storage); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/ArrowFunctionHandler.php b/src/Analyser/ExprHandler/ArrowFunctionHandler.php index 0cdfddf675d..d4306247c81 100644 --- a/src/Analyser/ExprHandler/ArrowFunctionHandler.php +++ b/src/Analyser/ExprHandler/ArrowFunctionHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ClosureTypeResolver; @@ -28,6 +29,7 @@ final class ArrowFunctionHandler implements ExprHandler public function __construct( private ClosureTypeResolver $closureTypeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -41,7 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $result = $nodeScopeResolver->processArrowFunctionNode($stmt, $expr, $scope, $storage, $nodeCallback, null); - return new ExpressionResult( + return $this->expressionResultFactory->create( $result->getScope(), hasYield: $result->hasYield(), isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 540119391e7..5f16804d271 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -25,6 +25,7 @@ use PHPStan\Analyser\ConditionalExpressionHolder; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExpressionTypeHolder; use PHPStan\Analyser\ExprHandler; @@ -95,6 +96,7 @@ public function __construct( private PhpVersion $phpVersion, private ExprPrinter $exprPrinter, private MatchHandler $matchHandler, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -298,7 +300,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $expr->expr, $nodeCallback, $context, - static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $storage, $nodeScopeResolver): ExpressionResult { + function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $storage, $nodeScopeResolver): ExpressionResult { $impurePoints = []; if ($expr instanceof AssignRef) { $referencedExpr = $expr->expr; @@ -338,7 +340,7 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex $scope = $scope->exitExpressionAssign($expr->expr); } - return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); }, true, ); @@ -380,7 +382,7 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex } } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), @@ -938,7 +940,7 @@ public function processAssignVar( new GetOffsetValueTypeExpr($assignedExpr, $dimExpr), $nodeCallback, $context, - static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), + fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), $enterExpressionAssign, ); $scope = $result->getScope(); @@ -1030,7 +1032,7 @@ public function processAssignVar( } // stored where processAssignVar is called - return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } private function createArrayDimFetchConditionalExpressionHolder( diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index 31af0b9c77f..17a35977c29 100644 --- a/src/Analyser/ExprHandler/AssignOpHandler.php +++ b/src/Analyser/ExprHandler/AssignOpHandler.php @@ -11,6 +11,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; @@ -42,6 +43,7 @@ public function __construct( private AssignHandler $assignHandler, private InitializerExprTypeResolver $initializerExprTypeResolver, private ImplicitToStringCallHelper $implicitToStringCallHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -62,7 +64,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $expr, $nodeCallback, $context, - static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $storage, $nodeScopeResolver): ExpressionResult { + function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $storage, $nodeScopeResolver): ExpressionResult { $originalScope = $scope; if ($expr instanceof Expr\AssignOp\Coalesce) { $scope = $scope->filterByFalseyValue( @@ -74,7 +76,7 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex if ($expr instanceof Expr\AssignOp\Coalesce) { $nodeScopeResolver->storeBeforeScope($storage, $expr, $originalScope); $isAlwaysTerminating = $exprResult->isAlwaysTerminating() && $originalScope->getType($expr->var)->isNull()->yes(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $exprResult->getScope()->mergeWith($originalScope), $exprResult->hasYield(), $isAlwaysTerminating, @@ -105,7 +107,7 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex $impurePoints = array_merge($impurePoints, $toStringResult->getImpurePoints()); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $assignResult->hasYield(), isAlwaysTerminating: $assignResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/BinaryOpHandler.php b/src/Analyser/ExprHandler/BinaryOpHandler.php index 0f604c86441..4c37485323d 100644 --- a/src/Analyser/ExprHandler/BinaryOpHandler.php +++ b/src/Analyser/ExprHandler/BinaryOpHandler.php @@ -14,6 +14,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\EqualityTypeSpecifyingHelper; @@ -66,6 +67,7 @@ public function __construct( private ImplicitToStringCallHelper $implicitToStringCallHelper, private ExprPrinter $exprPrinter, private EqualityTypeSpecifyingHelper $equalityTypeSpecifyingHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -101,7 +103,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } $scope = $rightResult->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating() || $rightResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/BitwiseNotHandler.php b/src/Analyser/ExprHandler/BitwiseNotHandler.php index de49fb09887..08ea1a0c1ed 100644 --- a/src/Analyser/ExprHandler/BitwiseNotHandler.php +++ b/src/Analyser/ExprHandler/BitwiseNotHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -28,6 +29,7 @@ final class BitwiseNotHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -41,7 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); - return new ExpressionResult( + return $this->expressionResultFactory->create( $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/BooleanAndHandler.php b/src/Analyser/ExprHandler/BooleanAndHandler.php index 6a56274b378..bd0fcf526af 100644 --- a/src/Analyser/ExprHandler/BooleanAndHandler.php +++ b/src/Analyser/ExprHandler/BooleanAndHandler.php @@ -10,6 +10,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ConditionalExpressionHolderHelper; @@ -44,6 +45,7 @@ final class BooleanAndHandler implements ExprHandler public function __construct( private NodeScopeResolver $nodeScopeResolver, private ConditionalExpressionHolderHelper $conditionalExpressionHolderHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -261,7 +263,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new BooleanAndNode($expr, $leftTruthyScope), $scope, $storage, $context); - return new ExpressionResult( + return $this->expressionResultFactory->create( $leftMergedWithRightScope, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/BooleanNotHandler.php b/src/Analyser/ExprHandler/BooleanNotHandler.php index 6bd2831fb27..082615dc3d5 100644 --- a/src/Analyser/ExprHandler/BooleanNotHandler.php +++ b/src/Analyser/ExprHandler/BooleanNotHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -27,6 +28,10 @@ final class BooleanNotHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof BooleanNot; @@ -37,7 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $exprResult->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/BooleanOrHandler.php b/src/Analyser/ExprHandler/BooleanOrHandler.php index d439a2c8082..9f2a00bef4e 100644 --- a/src/Analyser/ExprHandler/BooleanOrHandler.php +++ b/src/Analyser/ExprHandler/BooleanOrHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ConditionalExpressionHolderHelper; @@ -43,6 +44,7 @@ final class BooleanOrHandler implements ExprHandler public function __construct( private NodeScopeResolver $nodeScopeResolver, private ConditionalExpressionHolderHelper $conditionalExpressionHolderHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -303,7 +305,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new BooleanOrNode($expr, $leftFalseyScope), $scope, $storage, $context); - return new ExpressionResult( + return $this->expressionResultFactory->create( $leftMergedWithRightScope, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/CastHandler.php b/src/Analyser/ExprHandler/CastHandler.php index cbc3d70fcb0..f7837085055 100644 --- a/src/Analyser/ExprHandler/CastHandler.php +++ b/src/Analyser/ExprHandler/CastHandler.php @@ -13,6 +13,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -35,6 +36,7 @@ final class CastHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -49,7 +51,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $exprResult->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/CastStringHandler.php b/src/Analyser/ExprHandler/CastStringHandler.php index 4fdfc9adfc6..e8a4a3c9057 100644 --- a/src/Analyser/ExprHandler/CastStringHandler.php +++ b/src/Analyser/ExprHandler/CastStringHandler.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; @@ -33,6 +34,7 @@ final class CastStringHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ImplicitToStringCallHelper $implicitToStringCallHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -54,7 +56,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ClassConstFetchHandler.php b/src/Analyser/ExprHandler/ClassConstFetchHandler.php index 25199ed14f7..61e28d1cac5 100644 --- a/src/Analyser/ExprHandler/ClassConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ClassConstFetchHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -31,6 +32,7 @@ final class ClassConstFetchHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -83,7 +85,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $isAlwaysTerminating || $nameResult->isAlwaysTerminating(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/CloneHandler.php b/src/Analyser/ExprHandler/CloneHandler.php index 9d2f1bf6574..78534ceb1c7 100644 --- a/src/Analyser/ExprHandler/CloneHandler.php +++ b/src/Analyser/ExprHandler/CloneHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -29,6 +30,10 @@ final class CloneHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Clone_; @@ -38,7 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); - return new ExpressionResult( + return $this->expressionResultFactory->create( $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ClosureHandler.php b/src/Analyser/ExprHandler/ClosureHandler.php index cc70889d379..0961cf29c98 100644 --- a/src/Analyser/ExprHandler/ClosureHandler.php +++ b/src/Analyser/ExprHandler/ClosureHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ClosureTypeResolver; @@ -28,6 +29,7 @@ final class ClosureHandler implements ExprHandler public function __construct( private ClosureTypeResolver $closureTypeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -42,7 +44,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $processClosureResult = $nodeScopeResolver->processClosureNode($stmt, $expr, $scope, $storage, $nodeCallback, $context, null); $scope = $processClosureResult->applyByRefUseScope($processClosureResult->getScope()); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index eb566e0166d..2a6fab5a268 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; @@ -34,6 +35,7 @@ final class CoalesceHandler implements ExprHandler public function __construct( private NonNullabilityHelper $nonNullabilityHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -129,7 +131,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $scope->filterByTruthyValue(new Expr\Isset_([$expr->left]))->mergeWith($rightResult->getScope()); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $condResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $condResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ConstFetchHandler.php b/src/Analyser/ExprHandler/ConstFetchHandler.php index ba5ae2c71e1..6d825348e16 100644 --- a/src/Analyser/ExprHandler/ConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ConstFetchHandler.php @@ -9,6 +9,7 @@ use PHPStan\Analyser\ConstantResolver; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -33,6 +34,7 @@ final class ConstFetchHandler implements ExprHandler public function __construct( private ConstantResolver $constantResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -46,7 +48,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $nodeScopeResolver->callNodeCallback($nodeCallback, $expr->name, $scope, $storage); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/EmptyHandler.php b/src/Analyser/ExprHandler/EmptyHandler.php index 185850e6e6f..cba551bfd36 100644 --- a/src/Analyser/ExprHandler/EmptyHandler.php +++ b/src/Analyser/ExprHandler/EmptyHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; @@ -32,6 +33,7 @@ final class EmptyHandler implements ExprHandler public function __construct( private NonNullabilityHelper $nonNullabilityHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -92,7 +94,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $this->nonNullabilityHelper->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); $scope = $nodeScopeResolver->lookForUnsetAllowedUndefinedExpressions($scope, $expr->expr); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ErrorSuppressHandler.php b/src/Analyser/ExprHandler/ErrorSuppressHandler.php index 0e2e47fee9c..1189f6324a7 100644 --- a/src/Analyser/ExprHandler/ErrorSuppressHandler.php +++ b/src/Analyser/ExprHandler/ErrorSuppressHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -25,6 +26,10 @@ final class ErrorSuppressHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof ErrorSuppress; @@ -34,7 +39,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context); - return new ExpressionResult( + return $this->expressionResultFactory->create( $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/EvalHandler.php b/src/Analyser/ExprHandler/EvalHandler.php index c2e635c4880..62b16484e1a 100644 --- a/src/Analyser/ExprHandler/EvalHandler.php +++ b/src/Analyser/ExprHandler/EvalHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; @@ -29,6 +30,10 @@ final class EvalHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Eval_; @@ -44,7 +49,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $exprResult->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ExitHandler.php b/src/Analyser/ExprHandler/ExitHandler.php index 7734d8dea34..a2d4a7db273 100644 --- a/src/Analyser/ExprHandler/ExitHandler.php +++ b/src/Analyser/ExprHandler/ExitHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; @@ -28,6 +29,10 @@ final class ExitHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Exit_; @@ -51,7 +56,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: true, diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index d28a3225dee..8fb5bb55c83 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -16,6 +16,7 @@ use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\VoidToNullTypeTransformer; @@ -95,6 +96,7 @@ public function __construct( private bool $implicitThrows, #[AutowiredParameter] private bool $rememberPossiblyImpureFunctionValues, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -571,7 +573,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $scope->afterOpenSslCall($functionReflection->getName()); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php b/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php index eab62846f88..4ef762ad16d 100644 --- a/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php +++ b/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php @@ -6,6 +6,7 @@ use PhpParser\Node\Identifier; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\MutatingScope; use PHPStan\DependencyInjection\AutowiredService; @@ -19,6 +20,7 @@ final class ImplicitToStringCallHelper public function __construct( private PhpVersion $phpVersion, private MethodThrowPointHelper $methodThrowPointHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -35,7 +37,7 @@ public function processImplicitToStringCall(Expr $expr, MutatingScope $scope): E $toStringMethod = $scope->getMethodReflection($exprType, '__toString'); } if ($toStringMethod === null) { - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, @@ -67,7 +69,7 @@ public function processImplicitToStringCall(Expr $expr, MutatingScope $scope): E } } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/IncludeHandler.php b/src/Analyser/ExprHandler/IncludeHandler.php index 788a7c16522..c8556a88caf 100644 --- a/src/Analyser/ExprHandler/IncludeHandler.php +++ b/src/Analyser/ExprHandler/IncludeHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; @@ -30,6 +31,10 @@ final class IncludeHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Include_; @@ -46,7 +51,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $identifier = in_array($expr->type, [Include_::TYPE_INCLUDE, Include_::TYPE_INCLUDE_ONCE], true) ? 'include' : 'require'; $scope = $exprResult->getScope()->afterExtractCall(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/InstanceofHandler.php b/src/Analyser/ExprHandler/InstanceofHandler.php index 36933e466b3..0c1007743f8 100644 --- a/src/Analyser/ExprHandler/InstanceofHandler.php +++ b/src/Analyser/ExprHandler/InstanceofHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -38,6 +39,10 @@ final class InstanceofHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Instanceof_; @@ -60,7 +65,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $isAlwaysTerminating || $classResult->isAlwaysTerminating(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/InterpolatedStringHandler.php b/src/Analyser/ExprHandler/InterpolatedStringHandler.php index 51de44f579f..3e8672a012f 100644 --- a/src/Analyser/ExprHandler/InterpolatedStringHandler.php +++ b/src/Analyser/ExprHandler/InterpolatedStringHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; @@ -33,6 +34,7 @@ final class InterpolatedStringHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ImplicitToStringCallHelper $implicitToStringCallHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -65,7 +67,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $partResult->getScope(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/IssetHandler.php b/src/Analyser/ExprHandler/IssetHandler.php index da8e48431fd..a2becee723f 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.php @@ -15,6 +15,7 @@ use PhpParser\Node\VarLikeIdentifier; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; @@ -59,6 +60,7 @@ final class IssetHandler implements ExprHandler public function __construct( private NonNullabilityHelper $nonNullabilityHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -385,7 +387,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $this->nonNullabilityHelper->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/MatchHandler.php b/src/Analyser/ExprHandler/MatchHandler.php index c83532f62dc..b49f356b52f 100644 --- a/src/Analyser/ExprHandler/MatchHandler.php +++ b/src/Analyser/ExprHandler/MatchHandler.php @@ -16,6 +16,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\InternalThrowPoint; @@ -57,6 +58,7 @@ final class MatchHandler implements ExprHandler public function __construct( #[AutowiredParameter] private bool $treatPhpDocTypesAsCertain, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -502,7 +504,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $expr->cond = $expr->cond->getExpr(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index 77f5969ae7d..011e3e9a0d3 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -11,6 +11,7 @@ use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\MethodCallReturnTypeHelper; @@ -60,6 +61,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, #[AutowiredParameter] private bool $rememberPossiblyImpureFunctionValues, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -191,7 +193,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = array_merge($impurePoints, $argsResult->getImpurePoints()); $isAlwaysTerminating = $isAlwaysTerminating || $argsResult->isAlwaysTerminating(); - $result = new ExpressionResult( + $result = $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, @@ -219,7 +221,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $calledMethodScope = $nodeScopeResolver->processCalledMethod($methodReflection); if ($calledMethodScope !== null) { $scope = $scope->mergeInitializedProperties($calledMethodScope); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index 2360ccd8076..a59d73ac12a 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -11,6 +11,7 @@ use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; @@ -77,6 +78,7 @@ public function __construct( private PropertyReflectionFinder $propertyReflectionFinder, #[AutowiredParameter(ref: '%exceptions.implicitThrows%')] private bool $implicitThrows, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -215,7 +217,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $throwPoints[] = InternalThrowPoint::createImplicit($scope, $expr); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php index 864a4859275..d0ec6d41ca8 100644 --- a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php +++ b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php @@ -12,6 +12,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; @@ -37,6 +38,7 @@ final class NullsafeMethodCallHandler implements ExprHandler public function __construct( private NonNullabilityHelper $nonNullabilityHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -115,7 +117,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $scope->mergeWith($scopeBeforeNullsafe); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php index 0c9e190ff47..8ec6531c115 100644 --- a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php @@ -12,6 +12,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; @@ -37,6 +38,7 @@ final class NullsafePropertyFetchHandler implements ExprHandler public function __construct( private NonNullabilityHelper $nonNullabilityHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -94,7 +96,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex ), $nonNullabilityResult->getScope(), $storage, $nodeCallback, $context); $scope = $this->nonNullabilityHelper->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/PipeHandler.php b/src/Analyser/ExprHandler/PipeHandler.php index 93bd3554ef7..4ff195222bd 100644 --- a/src/Analyser/ExprHandler/PipeHandler.php +++ b/src/Analyser/ExprHandler/PipeHandler.php @@ -11,6 +11,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -32,6 +33,10 @@ final class PipeHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Pipe; @@ -84,7 +89,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $callResult = $nodeScopeResolver->processExprNode($stmt, $callExpr, $scope, $storage, $nodeCallback, $context); - return new ExpressionResult( + return $this->expressionResultFactory->create( $callResult->getScope(), hasYield: $callResult->hasYield(), isAlwaysTerminating: $callResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PostDecHandler.php b/src/Analyser/ExprHandler/PostDecHandler.php index 6c38de4a62e..1714b1d3ff2 100644 --- a/src/Analyser/ExprHandler/PostDecHandler.php +++ b/src/Analyser/ExprHandler/PostDecHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -26,6 +27,10 @@ final class PostDecHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof PostDec; @@ -44,7 +49,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, )->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PostIncHandler.php b/src/Analyser/ExprHandler/PostIncHandler.php index f26c45c50fe..4f9f304e30c 100644 --- a/src/Analyser/ExprHandler/PostIncHandler.php +++ b/src/Analyser/ExprHandler/PostIncHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -26,6 +27,10 @@ final class PostIncHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof PostInc; @@ -44,7 +49,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, )->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PreDecHandler.php b/src/Analyser/ExprHandler/PreDecHandler.php index 4abde925a80..f79b754181a 100644 --- a/src/Analyser/ExprHandler/PreDecHandler.php +++ b/src/Analyser/ExprHandler/PreDecHandler.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -40,6 +41,10 @@ final class PreDecHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof PreDec; @@ -107,7 +112,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, )->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PreIncHandler.php b/src/Analyser/ExprHandler/PreIncHandler.php index 1750b876542..f3504623cca 100644 --- a/src/Analyser/ExprHandler/PreIncHandler.php +++ b/src/Analyser/ExprHandler/PreIncHandler.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -41,6 +42,10 @@ final class PreIncHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof PreInc; @@ -108,7 +113,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, )->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PrintHandler.php b/src/Analyser/ExprHandler/PrintHandler.php index 9d8b220ccfe..9a3b61a14d3 100644 --- a/src/Analyser/ExprHandler/PrintHandler.php +++ b/src/Analyser/ExprHandler/PrintHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; @@ -31,6 +32,7 @@ final class PrintHandler implements ExprHandler public function __construct( private ImplicitToStringCallHelper $implicitToStringCallHelper, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -57,7 +59,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PropertyFetchHandler.php b/src/Analyser/ExprHandler/PropertyFetchHandler.php index 9be0c5c28be..f4ba7070756 100644 --- a/src/Analyser/ExprHandler/PropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/PropertyFetchHandler.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper; @@ -40,6 +41,7 @@ final class PropertyFetchHandler implements ExprHandler public function __construct( private PhpVersion $phpVersion, private PropertyReflectionFinder $propertyReflectionFinder, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -81,7 +83,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/ScalarHandler.php b/src/Analyser/ExprHandler/ScalarHandler.php index abd0dc63a4f..6f0db0d86f5 100644 --- a/src/Analyser/ExprHandler/ScalarHandler.php +++ b/src/Analyser/ExprHandler/ScalarHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -30,6 +31,7 @@ final class ScalarHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -41,7 +43,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index e24683ac8f1..3a6d490730c 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -14,6 +14,7 @@ use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\MethodCallReturnTypeHelper; @@ -68,6 +69,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, #[AutowiredParameter] private bool $rememberPossiblyImpureFunctionValues, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -279,7 +281,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = array_merge($impurePoints, $argsResult->getImpurePoints()); $isAlwaysTerminating = $isAlwaysTerminating || $argsResult->isAlwaysTerminating(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php index ec36cf64388..dd68417723f 100644 --- a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php @@ -11,6 +11,7 @@ use PhpParser\Node\VarLikeIdentifier; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper; @@ -40,6 +41,7 @@ final class StaticPropertyFetchHandler implements ExprHandler public function __construct( private PropertyReflectionFinder $propertyReflectionFinder, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -80,7 +82,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $nameResult->getScope(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/TernaryHandler.php b/src/Analyser/ExprHandler/TernaryHandler.php index 3dcc769ad36..b1385b149b6 100644 --- a/src/Analyser/ExprHandler/TernaryHandler.php +++ b/src/Analyser/ExprHandler/TernaryHandler.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -33,6 +34,7 @@ final class TernaryHandler implements ExprHandler public function __construct( private NodeScopeResolver $nodeScopeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -144,7 +146,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } } - return new ExpressionResult( + return $this->expressionResultFactory->create( $finalScope, hasYield: $ternaryCondResult->hasYield(), isAlwaysTerminating: $ternaryCondResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ThrowHandler.php b/src/Analyser/ExprHandler/ThrowHandler.php index 63c9b4720e8..7c283decba4 100644 --- a/src/Analyser/ExprHandler/ThrowHandler.php +++ b/src/Analyser/ExprHandler/ThrowHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\InternalThrowPoint; @@ -28,6 +29,10 @@ final class ThrowHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Throw_; @@ -37,7 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()->enterThrow()); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: true, diff --git a/src/Analyser/ExprHandler/UnaryMinusHandler.php b/src/Analyser/ExprHandler/UnaryMinusHandler.php index 2bc2d872380..0da8e3d46a4 100644 --- a/src/Analyser/ExprHandler/UnaryMinusHandler.php +++ b/src/Analyser/ExprHandler/UnaryMinusHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -28,6 +29,7 @@ final class UnaryMinusHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -41,7 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); - return new ExpressionResult( + return $this->expressionResultFactory->create( $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/UnaryPlusHandler.php b/src/Analyser/ExprHandler/UnaryPlusHandler.php index 676110b904f..3f2c6bcbbf9 100644 --- a/src/Analyser/ExprHandler/UnaryPlusHandler.php +++ b/src/Analyser/ExprHandler/UnaryPlusHandler.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -28,6 +29,7 @@ final class UnaryPlusHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -41,7 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); - return new ExpressionResult( + return $this->expressionResultFactory->create( $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/VariableHandler.php b/src/Analyser/ExprHandler/VariableHandler.php index e104b9fed42..f2c6f414207 100644 --- a/src/Analyser/ExprHandler/VariableHandler.php +++ b/src/Analyser/ExprHandler/VariableHandler.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; @@ -34,6 +35,10 @@ final class VariableHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Variable; @@ -89,7 +94,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $nameResult->isAlwaysTerminating(); $scope = $nameResult->getScope(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, $hasYield, $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php index fcf77547e33..8e26801f1e0 100644 --- a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -25,6 +26,10 @@ final class AlwaysRememberedExprHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof AlwaysRememberedExpr; @@ -44,7 +49,7 @@ public function processExpr( $innerResult = $nodeScopeResolver->processExprNode($stmt, $innerExpr, $scope, $storage, $nodeCallback, $context); $scope = $innerResult->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $innerResult->hasYield(), isAlwaysTerminating: $innerResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php index c2bf0d37fab..071f56be856 100644 --- a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -25,6 +26,10 @@ final class ExistingArrayDimFetchHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof ExistingArrayDimFetch; @@ -35,7 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php index b358f70c8df..4b91658cc82 100644 --- a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -26,6 +27,10 @@ final class FunctionCallableNodeHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof FunctionCallableNode; @@ -46,7 +51,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $nameResult->isAlwaysTerminating(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php index a9de984485e..241122ccf84 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -25,6 +26,10 @@ final class GetIterableKeyTypeExprHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof GetIterableKeyTypeExpr; @@ -35,7 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php index 261c364ffd3..040732cfac3 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -25,6 +26,10 @@ final class GetIterableValueTypeExprHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof GetIterableValueTypeExpr; @@ -35,7 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php index 09922c7daa7..539ecc7a3b1 100644 --- a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -25,6 +26,10 @@ final class GetOffsetValueTypeExprHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof GetOffsetValueTypeExpr; @@ -35,7 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php index eb552e3a544..3ce468022a9 100644 --- a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -26,6 +27,10 @@ final class InstantiationCallableNodeHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof InstantiationCallableNode; @@ -46,7 +51,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $classResult->isAlwaysTerminating(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php index f2d224bc91a..c94173c354f 100644 --- a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -27,6 +28,10 @@ final class MethodCallableNodeHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof MethodCallableNode; @@ -49,7 +54,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $nameResult->isAlwaysTerminating(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php index c952fcc1297..e916eb9ec69 100644 --- a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -25,6 +26,10 @@ final class NativeTypeExprHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof NativeTypeExpr; @@ -35,7 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php index 7893990b1a3..ea08496e209 100644 --- a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -29,6 +30,7 @@ final class OriginalPropertyTypeExprHandler implements ExprHandler public function __construct( private PropertyReflectionFinder $propertyReflectionFinder, + private ExpressionResultFactory $expressionResultFactory, ) { } @@ -43,7 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php index cd764c40dbf..5c986631ba1 100644 --- a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -27,6 +28,10 @@ final class SetExistingOffsetValueTypeExprHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof SetExistingOffsetValueTypeExpr; @@ -37,7 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php index 92c41c6b516..a8ffc98a0bb 100644 --- a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -27,6 +28,10 @@ final class SetOffsetValueTypeExprHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof SetOffsetValueTypeExpr; @@ -37,7 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php index 10467a171a5..0eef43563b0 100644 --- a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -27,6 +28,10 @@ final class StaticMethodCallableNodeHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof StaticMethodCallableNode; @@ -55,7 +60,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $isAlwaysTerminating || $nameResult->isAlwaysTerminating(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php index 81c6c9f08f8..53766b68323 100644 --- a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -25,6 +26,10 @@ final class TypeExprHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof TypeExpr; @@ -35,7 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php index 0c2c4741831..995219a4f05 100644 --- a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -25,6 +26,10 @@ final class UnsetOffsetExprHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof UnsetOffsetExpr; @@ -35,7 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // because this is a virtual node handler, the caller will only be interested in the type // we don't need to process the inner expr - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/YieldFromHandler.php b/src/Analyser/ExprHandler/YieldFromHandler.php index 7b86f00abb2..9e148f388f7 100644 --- a/src/Analyser/ExprHandler/YieldFromHandler.php +++ b/src/Analyser/ExprHandler/YieldFromHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; @@ -31,6 +32,10 @@ final class YieldFromHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof YieldFrom; @@ -52,7 +57,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $exprResult->getScope(); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: true, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/YieldHandler.php b/src/Analyser/ExprHandler/YieldHandler.php index 48fb166ee70..583907a17b6 100644 --- a/src/Analyser/ExprHandler/YieldHandler.php +++ b/src/Analyser/ExprHandler/YieldHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; @@ -31,6 +32,10 @@ final class YieldHandler implements ExprHandler { + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Yield_; @@ -82,7 +87,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $isAlwaysTerminating || $valueResult->isAlwaysTerminating(); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: true, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 746c518953d..42e49e004aa 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -2,6 +2,9 @@ namespace PHPStan\Analyser; +use PHPStan\DependencyInjection\GenerateFactory; + +#[GenerateFactory(interface: ExpressionResultFactory::class)] final class ExpressionResult { diff --git a/src/Analyser/ExpressionResultFactory.php b/src/Analyser/ExpressionResultFactory.php new file mode 100644 index 00000000000..f724e6fdcb4 --- /dev/null +++ b/src/Analyser/ExpressionResultFactory.php @@ -0,0 +1,24 @@ +expressionResultFactory->create($scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []); } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: false, @@ -3135,7 +3136,7 @@ public function processArrowFunctionNode( $this->callNodeCallback($nodeCallback, new InArrowFunctionNode($arrowFunctionType, $expr), $arrowFunctionScope, $storage); $exprResult = $this->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $storage, $nodeCallback, ExpressionContext::createTopLevel()); - return new ExpressionResult($scope, false, $exprResult->isAlwaysTerminating(), $exprResult->getThrowPoints(), $exprResult->getImpurePoints()); + return $this->expressionResultFactory->create($scope, false, $exprResult->isAlwaysTerminating(), $exprResult->getThrowPoints(), $exprResult->getImpurePoints()); } /** @@ -3838,7 +3839,7 @@ public function processArgs( } // not storing this, it's scope after processing all args - return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } /** @@ -3975,7 +3976,7 @@ public function processVirtualAssign(MutatingScope $scope, ExpressionResultStora $assignedExpr, new VirtualAssignNodeCallback($nodeCallback), ExpressionContext::createDeep(), - static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), + fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), false, ); } diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index d8d0ba20e1c..59cdbf3eab4 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Analyser; use PHPStan\Analyser\AnalyserResultFinalizer; use PHPStan\Analyser\Error; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; use PHPStan\Analyser\Fiber\FiberNodeScopeResolver; use PHPStan\Analyser\FileAnalyser; @@ -118,6 +119,7 @@ protected function createNodeScopeResolver(): NodeScopeResolver self::getContainer()->getParameter('exceptions')['implicitThrows'], $this->shouldTreatPhpDocTypesAsCertain(), self::getContainer()->getByType(ImplicitToStringCallHelper::class), + self::getContainer()->getByType(ExpressionResultFactory::class), ); } diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 7723560fd06..3cda273ecf0 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; use PHPStan\Analyser\Fiber\FiberNodeScopeResolver; use PHPStan\Analyser\MutatingScope; @@ -93,6 +94,7 @@ protected static function createNodeScopeResolver(): NodeScopeResolver $container->getParameter('exceptions')['implicitThrows'], $container->getParameter('treatPhpDocTypesAsCertain'), $container->getByType(ImplicitToStringCallHelper::class), + $container->getByType(ExpressionResultFactory::class), ); } diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 39da6a5be9e..ded70fca9e3 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -834,6 +834,7 @@ private function createAnalyser(): Analyser true, $this->shouldTreatPhpDocTypesAsCertain(), $container->getByType(ImplicitToStringCallHelper::class), + $container->getByType(ExpressionResultFactory::class), ); $lexer = new Lexer(); $fileAnalyser = new FileAnalyser( diff --git a/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverRuleTest.php b/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverRuleTest.php index 0a1804341a6..fae07c69bb5 100644 --- a/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverRuleTest.php +++ b/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverRuleTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser\Fiber; use PhpParser\Node; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; @@ -138,6 +139,7 @@ protected function createNodeScopeResolver(): NodeScopeResolver self::getContainer()->getParameter('exceptions')['implicitThrows'], $this->shouldTreatPhpDocTypesAsCertain(), self::getContainer()->getByType(ImplicitToStringCallHelper::class), + self::getContainer()->getByType(ExpressionResultFactory::class), ); } diff --git a/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverTest.php b/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverTest.php index ae9aa1ec4c1..608bfee99ec 100644 --- a/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser\Fiber; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\DependencyInjection\Type\ParameterClosureThisExtensionProvider; @@ -71,6 +72,7 @@ protected static function createNodeScopeResolver(): NodeScopeResolver $container->getParameter('exceptions')['implicitThrows'], $container->getParameter('treatPhpDocTypesAsCertain'), $container->getByType(ImplicitToStringCallHelper::class), + $container->getByType(ExpressionResultFactory::class), ); } From 26a165de29b1d3e44ddeceb30741f3ee7114318e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Jun 2026 11:28:09 +0200 Subject: [PATCH 02/22] ExpressionResult - add beforeScope --- .../ExprHandler/ArrayDimFetchHandler.php | 3 +++ src/Analyser/ExprHandler/ArrayHandler.php | 2 ++ .../ExprHandler/ArrowFunctionHandler.php | 1 + src/Analyser/ExprHandler/AssignHandler.php | 10 +++++++--- src/Analyser/ExprHandler/AssignOpHandler.php | 3 +++ src/Analyser/ExprHandler/BinaryOpHandler.php | 2 ++ .../ExprHandler/BitwiseNotHandler.php | 1 + .../ExprHandler/BooleanAndHandler.php | 1 + .../ExprHandler/BooleanNotHandler.php | 2 ++ src/Analyser/ExprHandler/BooleanOrHandler.php | 1 + src/Analyser/ExprHandler/CastHandler.php | 2 ++ .../ExprHandler/CastStringHandler.php | 2 ++ .../ExprHandler/ClassConstFetchHandler.php | 2 ++ src/Analyser/ExprHandler/CloneHandler.php | 1 + src/Analyser/ExprHandler/ClosureHandler.php | 4 ++-- src/Analyser/ExprHandler/CoalesceHandler.php | 2 ++ .../ExprHandler/ConstFetchHandler.php | 1 + src/Analyser/ExprHandler/EmptyHandler.php | 2 ++ .../ExprHandler/ErrorSuppressHandler.php | 1 + src/Analyser/ExprHandler/EvalHandler.php | 2 ++ src/Analyser/ExprHandler/ExitHandler.php | 2 ++ src/Analyser/ExprHandler/FuncCallHandler.php | 2 ++ .../Helper/ImplicitToStringCallHelper.php | 2 ++ src/Analyser/ExprHandler/IncludeHandler.php | 2 ++ .../ExprHandler/InstanceofHandler.php | 2 ++ .../ExprHandler/InterpolatedStringHandler.php | 2 ++ src/Analyser/ExprHandler/IssetHandler.php | 2 ++ src/Analyser/ExprHandler/MatchHandler.php | 2 ++ .../ExprHandler/MethodCallHandler.php | 3 +++ src/Analyser/ExprHandler/NewHandler.php | 2 ++ .../ExprHandler/NullsafeMethodCallHandler.php | 2 ++ .../NullsafePropertyFetchHandler.php | 2 ++ src/Analyser/ExprHandler/PipeHandler.php | 1 + src/Analyser/ExprHandler/PostDecHandler.php | 19 +++++++++---------- src/Analyser/ExprHandler/PostIncHandler.php | 19 +++++++++---------- src/Analyser/ExprHandler/PreDecHandler.php | 19 +++++++++---------- src/Analyser/ExprHandler/PreIncHandler.php | 19 +++++++++---------- src/Analyser/ExprHandler/PrintHandler.php | 2 ++ .../ExprHandler/PropertyFetchHandler.php | 2 ++ src/Analyser/ExprHandler/ScalarHandler.php | 1 + .../ExprHandler/StaticCallHandler.php | 2 ++ .../StaticPropertyFetchHandler.php | 2 ++ src/Analyser/ExprHandler/TernaryHandler.php | 1 + src/Analyser/ExprHandler/ThrowHandler.php | 1 + .../ExprHandler/UnaryMinusHandler.php | 1 + src/Analyser/ExprHandler/UnaryPlusHandler.php | 1 + src/Analyser/ExprHandler/VariableHandler.php | 2 ++ .../Virtual/AlwaysRememberedExprHandler.php | 2 ++ .../Virtual/ExistingArrayDimFetchHandler.php | 1 + .../Virtual/FunctionCallableNodeHandler.php | 2 ++ .../Virtual/GetIterableKeyTypeExprHandler.php | 1 + .../GetIterableValueTypeExprHandler.php | 1 + .../Virtual/GetOffsetValueTypeExprHandler.php | 1 + .../InstantiationCallableNodeHandler.php | 2 ++ .../Virtual/MethodCallableNodeHandler.php | 2 ++ .../Virtual/NativeTypeExprHandler.php | 1 + .../OriginalPropertyTypeExprHandler.php | 1 + .../SetExistingOffsetValueTypeExprHandler.php | 1 + .../Virtual/SetOffsetValueTypeExprHandler.php | 1 + .../StaticMethodCallableNodeHandler.php | 2 ++ .../ExprHandler/Virtual/TypeExprHandler.php | 1 + .../Virtual/UnsetOffsetExprHandler.php | 1 + src/Analyser/ExprHandler/YieldFromHandler.php | 2 ++ src/Analyser/ExprHandler/YieldHandler.php | 2 ++ src/Analyser/ExpressionResult.php | 6 ++++++ src/Analyser/ExpressionResultFactory.php | 1 + src/Analyser/NodeScopeResolver.php | 9 +++++---- 67 files changed, 153 insertions(+), 49 deletions(-) diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index f82489c876b..59ea71f1e31 100644 --- a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php @@ -81,12 +81,14 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; if ($expr->dim === null) { $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $varResult->getScope(); return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), @@ -116,6 +118,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $dimResult->hasYield() || $varResult->hasYield(), isAlwaysTerminating: $dimResult->isAlwaysTerminating() || $varResult->isAlwaysTerminating(), throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index 580ab998b6c..78eb2b5491d 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -74,6 +74,7 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $itemNodes = []; $hasYield = false; $throwPoints = []; @@ -102,6 +103,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ArrowFunctionHandler.php b/src/Analyser/ExprHandler/ArrowFunctionHandler.php index d4306247c81..b98fc9c85de 100644 --- a/src/Analyser/ExprHandler/ArrowFunctionHandler.php +++ b/src/Analyser/ExprHandler/ArrowFunctionHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $result->getScope(), + beforeScope: $scope, hasYield: $result->hasYield(), isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 5f16804d271..a9248dc6d3a 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -291,6 +291,7 @@ public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $e public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $result = $this->processAssignVar( $nodeScopeResolver, $scope, @@ -301,6 +302,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, $context, function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $storage, $nodeScopeResolver): ExpressionResult { + $beforeScope = $scope; $impurePoints = []; if ($expr instanceof AssignRef) { $referencedExpr = $expr->expr; @@ -340,7 +342,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $scope = $scope->exitExpressionAssign($expr->expr); } - return $this->expressionResultFactory->create($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($scope, $beforeScope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); }, true, ); @@ -384,6 +386,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), throwPoints: $result->getThrowPoints(), @@ -410,6 +413,7 @@ public function processAssignVar( bool $enterExpressionAssign, ): ExpressionResult { + $beforeScope = $scope; $nodeScopeResolver->callNodeCallback($nodeCallback, $var, $enterExpressionAssign ? $scope->enterExpressionAssign($var) : $scope, $storage); $hasYield = false; $throwPoints = []; @@ -940,7 +944,7 @@ public function processAssignVar( new GetOffsetValueTypeExpr($assignedExpr, $dimExpr), $nodeCallback, $context, - fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), + fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), $enterExpressionAssign, ); $scope = $result->getScope(); @@ -1032,7 +1036,7 @@ public function processAssignVar( } // stored where processAssignVar is called - return $this->expressionResultFactory->create($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($scope, $beforeScope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } private function createArrayDimFetchConditionalExpressionHolder( diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index 17a35977c29..5d0d4c8f842 100644 --- a/src/Analyser/ExprHandler/AssignOpHandler.php +++ b/src/Analyser/ExprHandler/AssignOpHandler.php @@ -55,6 +55,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $assignResult = $this->assignHandler->processAssignVar( $nodeScopeResolver, $scope, @@ -78,6 +79,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $isAlwaysTerminating = $exprResult->isAlwaysTerminating() && $originalScope->getType($expr->var)->isNull()->yes(); return $this->expressionResultFactory->create( $exprResult->getScope()->mergeWith($originalScope), + $originalScope, $exprResult->hasYield(), $isAlwaysTerminating, $exprResult->getThrowPoints(), @@ -109,6 +111,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $assignResult->hasYield(), isAlwaysTerminating: $assignResult->isAlwaysTerminating(), throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/BinaryOpHandler.php b/src/Analyser/ExprHandler/BinaryOpHandler.php index 4c37485323d..70d7841012a 100644 --- a/src/Analyser/ExprHandler/BinaryOpHandler.php +++ b/src/Analyser/ExprHandler/BinaryOpHandler.php @@ -85,6 +85,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $leftResult = $nodeScopeResolver->processExprNode($stmt, $expr->left, $scope, $storage, $nodeCallback, $context->enterDeep()); $rightResult = $nodeScopeResolver->processExprNode($stmt, $expr->right, $leftResult->getScope(), $storage, $nodeCallback, $context->enterDeep()); $throwPoints = array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()); @@ -105,6 +106,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating() || $rightResult->isAlwaysTerminating(), throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/BitwiseNotHandler.php b/src/Analyser/ExprHandler/BitwiseNotHandler.php index 08ea1a0c1ed..ae9ec208bd5 100644 --- a/src/Analyser/ExprHandler/BitwiseNotHandler.php +++ b/src/Analyser/ExprHandler/BitwiseNotHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), + beforeScope: $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/BooleanAndHandler.php b/src/Analyser/ExprHandler/BooleanAndHandler.php index bd0fcf526af..26be561a74e 100644 --- a/src/Analyser/ExprHandler/BooleanAndHandler.php +++ b/src/Analyser/ExprHandler/BooleanAndHandler.php @@ -265,6 +265,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $leftMergedWithRightScope, + beforeScope: $scope, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating(), throwPoints: array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), diff --git a/src/Analyser/ExprHandler/BooleanNotHandler.php b/src/Analyser/ExprHandler/BooleanNotHandler.php index 082615dc3d5..08091ce1dc0 100644 --- a/src/Analyser/ExprHandler/BooleanNotHandler.php +++ b/src/Analyser/ExprHandler/BooleanNotHandler.php @@ -39,11 +39,13 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/BooleanOrHandler.php b/src/Analyser/ExprHandler/BooleanOrHandler.php index 9f2a00bef4e..46e8bc28631 100644 --- a/src/Analyser/ExprHandler/BooleanOrHandler.php +++ b/src/Analyser/ExprHandler/BooleanOrHandler.php @@ -307,6 +307,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $leftMergedWithRightScope, + beforeScope: $scope, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating(), throwPoints: array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), diff --git a/src/Analyser/ExprHandler/CastHandler.php b/src/Analyser/ExprHandler/CastHandler.php index f7837085055..99085f9b800 100644 --- a/src/Analyser/ExprHandler/CastHandler.php +++ b/src/Analyser/ExprHandler/CastHandler.php @@ -48,11 +48,13 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/CastStringHandler.php b/src/Analyser/ExprHandler/CastStringHandler.php index e8a4a3c9057..32f31ec60a1 100644 --- a/src/Analyser/ExprHandler/CastStringHandler.php +++ b/src/Analyser/ExprHandler/CastStringHandler.php @@ -46,6 +46,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $impurePoints = $exprResult->getImpurePoints(); $throwPoints = $exprResult->getThrowPoints(); @@ -58,6 +59,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ClassConstFetchHandler.php b/src/Analyser/ExprHandler/ClassConstFetchHandler.php index 61e28d1cac5..3283d8f4153 100644 --- a/src/Analyser/ExprHandler/ClassConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ClassConstFetchHandler.php @@ -58,6 +58,7 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $hasYield = false; $throwPoints = []; $impurePoints = []; @@ -87,6 +88,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/CloneHandler.php b/src/Analyser/ExprHandler/CloneHandler.php index 78534ceb1c7..ee1951e68d5 100644 --- a/src/Analyser/ExprHandler/CloneHandler.php +++ b/src/Analyser/ExprHandler/CloneHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), + beforeScope: $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/ClosureHandler.php b/src/Analyser/ExprHandler/ClosureHandler.php index 0961cf29c98..e0e742e812d 100644 --- a/src/Analyser/ExprHandler/ClosureHandler.php +++ b/src/Analyser/ExprHandler/ClosureHandler.php @@ -42,10 +42,10 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { $processClosureResult = $nodeScopeResolver->processClosureNode($stmt, $expr, $scope, $storage, $nodeCallback, $context, null); - $scope = $processClosureResult->applyByRefUseScope($processClosureResult->getScope()); return $this->expressionResultFactory->create( - $scope, + $processClosureResult->applyByRefUseScope($processClosureResult->getScope()), + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index 2a6fab5a268..562a4f9075c 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -116,6 +116,7 @@ public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $e public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $nonNullabilityResult = $this->nonNullabilityHelper->ensureNonNullability($scope, $expr->left); $condScope = $nodeScopeResolver->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->left); $condResult = $nodeScopeResolver->processExprNode($stmt, $expr->left, $condScope, $storage, $nodeCallback, $context->enterDeep()); @@ -133,6 +134,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $condResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $condResult->isAlwaysTerminating(), throwPoints: array_merge($condResult->getThrowPoints(), $rightResult->getThrowPoints()), diff --git a/src/Analyser/ExprHandler/ConstFetchHandler.php b/src/Analyser/ExprHandler/ConstFetchHandler.php index 6d825348e16..23510dbbaba 100644 --- a/src/Analyser/ExprHandler/ConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ConstFetchHandler.php @@ -50,6 +50,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/EmptyHandler.php b/src/Analyser/ExprHandler/EmptyHandler.php index cba551bfd36..d5f932b2b14 100644 --- a/src/Analyser/ExprHandler/EmptyHandler.php +++ b/src/Analyser/ExprHandler/EmptyHandler.php @@ -87,6 +87,7 @@ public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $e public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $nonNullabilityResult = $this->nonNullabilityHelper->ensureNonNullability($scope, $expr->expr); $scope = $nodeScopeResolver->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->expr); $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); @@ -96,6 +97,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/ErrorSuppressHandler.php b/src/Analyser/ExprHandler/ErrorSuppressHandler.php index 1189f6324a7..700d260261a 100644 --- a/src/Analyser/ExprHandler/ErrorSuppressHandler.php +++ b/src/Analyser/ExprHandler/ErrorSuppressHandler.php @@ -41,6 +41,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), + beforeScope: $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/EvalHandler.php b/src/Analyser/ExprHandler/EvalHandler.php index 62b16484e1a..d1b7160701f 100644 --- a/src/Analyser/ExprHandler/EvalHandler.php +++ b/src/Analyser/ExprHandler/EvalHandler.php @@ -46,11 +46,13 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createImplicit($scope, $expr)]), diff --git a/src/Analyser/ExprHandler/ExitHandler.php b/src/Analyser/ExprHandler/ExitHandler.php index a2d4a7db273..0cba81da42c 100644 --- a/src/Analyser/ExprHandler/ExitHandler.php +++ b/src/Analyser/ExprHandler/ExitHandler.php @@ -40,6 +40,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $kind = $expr->getAttribute('kind', Exit_::KIND_EXIT); $identifier = $kind === Exit_::KIND_DIE ? 'die' : 'exit'; $impurePoints = [ @@ -58,6 +59,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: true, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index 8fb5bb55c83..673d0159ba9 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -108,6 +108,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $parametersAcceptor = null; $functionReflection = null; $throwPoints = []; @@ -575,6 +576,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php b/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php index 4ef762ad16d..9303995b032 100644 --- a/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php +++ b/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php @@ -39,6 +39,7 @@ public function processImplicitToStringCall(Expr $expr, MutatingScope $scope): E if ($toStringMethod === null) { return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], @@ -71,6 +72,7 @@ public function processImplicitToStringCall(Expr $expr, MutatingScope $scope): E return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/IncludeHandler.php b/src/Analyser/ExprHandler/IncludeHandler.php index c8556a88caf..44df8e73801 100644 --- a/src/Analyser/ExprHandler/IncludeHandler.php +++ b/src/Analyser/ExprHandler/IncludeHandler.php @@ -47,12 +47,14 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $identifier = in_array($expr->type, [Include_::TYPE_INCLUDE, Include_::TYPE_INCLUDE_ONCE], true) ? 'include' : 'require'; $scope = $exprResult->getScope()->afterExtractCall(); return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createImplicit($scope, $expr)]), diff --git a/src/Analyser/ExprHandler/InstanceofHandler.php b/src/Analyser/ExprHandler/InstanceofHandler.php index 0c1007743f8..077e965ffdc 100644 --- a/src/Analyser/ExprHandler/InstanceofHandler.php +++ b/src/Analyser/ExprHandler/InstanceofHandler.php @@ -50,6 +50,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $exprResult->hasYield(); $throwPoints = $exprResult->getThrowPoints(); @@ -67,6 +68,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/InterpolatedStringHandler.php b/src/Analyser/ExprHandler/InterpolatedStringHandler.php index 3e8672a012f..df25ce12114 100644 --- a/src/Analyser/ExprHandler/InterpolatedStringHandler.php +++ b/src/Analyser/ExprHandler/InterpolatedStringHandler.php @@ -46,6 +46,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $hasYield = false; $throwPoints = []; $impurePoints = []; @@ -69,6 +70,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/IssetHandler.php b/src/Analyser/ExprHandler/IssetHandler.php index a2becee723f..001ee84f127 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.php @@ -346,6 +346,7 @@ public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $e public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $hasYield = false; $throwPoints = []; $impurePoints = []; @@ -389,6 +390,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/MatchHandler.php b/src/Analyser/ExprHandler/MatchHandler.php index b49f356b52f..42d69ee30c7 100644 --- a/src/Analyser/ExprHandler/MatchHandler.php +++ b/src/Analyser/ExprHandler/MatchHandler.php @@ -209,6 +209,7 @@ public function getArmScopesAndTypes(MutatingScope $scope, Match_ $expr): array public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $deepContext = $context->enterDeep(); $condType = $scope->getType($expr->cond); $condNativeType = $scope->getNativeType($expr->cond); @@ -506,6 +507,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index 011e3e9a0d3..49b5b113759 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -73,6 +73,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $originalScope = $scope; if ( ($expr->var instanceof Expr\Closure || $expr->var instanceof Expr\ArrowFunction) @@ -195,6 +196,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $result = $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, @@ -223,6 +225,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $scope->mergeInitializedProperties($calledMethodScope); return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), throwPoints: $result->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index a59d73ac12a..da9eddc93a9 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -90,6 +90,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $parametersAcceptor = null; $constructorReflection = null; $classReflection = null; @@ -219,6 +220,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php index d0ec6d41ca8..05a94ad2668 100644 --- a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php +++ b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php @@ -86,6 +86,7 @@ public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $e public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $scopeBeforeNullsafe = $scope; $varType = $scope->getType($expr->var); @@ -119,6 +120,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php index 8ec6531c115..d9df195fd37 100644 --- a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php @@ -86,6 +86,7 @@ public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $e public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $nonNullabilityResult = $this->nonNullabilityHelper->ensureShallowNonNullability($scope, $scope, $expr->var); $attributes = array_merge($expr->getAttributes(), ['virtualNullsafePropertyFetch' => true]); unset($attributes[ExprPrinter::ATTRIBUTE_CACHE_KEY]); @@ -98,6 +99,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PipeHandler.php b/src/Analyser/ExprHandler/PipeHandler.php index 4ff195222bd..e6d010e2bf4 100644 --- a/src/Analyser/ExprHandler/PipeHandler.php +++ b/src/Analyser/ExprHandler/PipeHandler.php @@ -91,6 +91,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $callResult->getScope(), + beforeScope: $scope, hasYield: $callResult->hasYield(), isAlwaysTerminating: $callResult->isAlwaysTerminating(), throwPoints: $callResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PostDecHandler.php b/src/Analyser/ExprHandler/PostDecHandler.php index 1714b1d3ff2..c28a4a0175e 100644 --- a/src/Analyser/ExprHandler/PostDecHandler.php +++ b/src/Analyser/ExprHandler/PostDecHandler.php @@ -40,17 +40,16 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); - $scope = $nodeScopeResolver->processVirtualAssign( - $varResult->getScope(), - $storage, - $stmt, - $expr->var, - new PreDec($expr->var), - $nodeCallback, - )->getScope(); - return $this->expressionResultFactory->create( - $scope, + $nodeScopeResolver->processVirtualAssign( + $varResult->getScope(), + $storage, + $stmt, + $expr->var, + new PreDec($expr->var), + $nodeCallback, + )->getScope(), + beforeScope: $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PostIncHandler.php b/src/Analyser/ExprHandler/PostIncHandler.php index 4f9f304e30c..f54fa7d4e0a 100644 --- a/src/Analyser/ExprHandler/PostIncHandler.php +++ b/src/Analyser/ExprHandler/PostIncHandler.php @@ -40,17 +40,16 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); - $scope = $nodeScopeResolver->processVirtualAssign( - $varResult->getScope(), - $storage, - $stmt, - $expr->var, - new PreInc($expr->var), - $nodeCallback, - )->getScope(); - return $this->expressionResultFactory->create( - $scope, + $nodeScopeResolver->processVirtualAssign( + $varResult->getScope(), + $storage, + $stmt, + $expr->var, + new PreInc($expr->var), + $nodeCallback, + )->getScope(), + beforeScope: $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PreDecHandler.php b/src/Analyser/ExprHandler/PreDecHandler.php index f79b754181a..344dde7a6ad 100644 --- a/src/Analyser/ExprHandler/PreDecHandler.php +++ b/src/Analyser/ExprHandler/PreDecHandler.php @@ -103,17 +103,16 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); - $scope = $nodeScopeResolver->processVirtualAssign( - $varResult->getScope(), - $storage, - $stmt, - $expr->var, - $expr, - $nodeCallback, - )->getScope(); - return $this->expressionResultFactory->create( - $scope, + $nodeScopeResolver->processVirtualAssign( + $varResult->getScope(), + $storage, + $stmt, + $expr->var, + $expr, + $nodeCallback, + )->getScope(), + beforeScope: $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PreIncHandler.php b/src/Analyser/ExprHandler/PreIncHandler.php index f3504623cca..9e6d026f101 100644 --- a/src/Analyser/ExprHandler/PreIncHandler.php +++ b/src/Analyser/ExprHandler/PreIncHandler.php @@ -104,17 +104,16 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); - $scope = $nodeScopeResolver->processVirtualAssign( - $varResult->getScope(), - $storage, - $stmt, - $expr->var, - $expr, - $nodeCallback, - )->getScope(); - return $this->expressionResultFactory->create( - $scope, + $nodeScopeResolver->processVirtualAssign( + $varResult->getScope(), + $storage, + $stmt, + $expr->var, + $expr, + $nodeCallback, + )->getScope(), + beforeScope: $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PrintHandler.php b/src/Analyser/ExprHandler/PrintHandler.php index 9a3b61a14d3..d495f8b63ca 100644 --- a/src/Analyser/ExprHandler/PrintHandler.php +++ b/src/Analyser/ExprHandler/PrintHandler.php @@ -49,6 +49,7 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $throwPoints = $exprResult->getThrowPoints(); $impurePoints = $exprResult->getImpurePoints(); @@ -61,6 +62,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/PropertyFetchHandler.php b/src/Analyser/ExprHandler/PropertyFetchHandler.php index f4ba7070756..06d78067198 100644 --- a/src/Analyser/ExprHandler/PropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/PropertyFetchHandler.php @@ -53,6 +53,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $scopeBeforeVar = $scope; $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $varResult->hasYield(); @@ -85,6 +86,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ScalarHandler.php b/src/Analyser/ExprHandler/ScalarHandler.php index 6f0db0d86f5..ade6dc84023 100644 --- a/src/Analyser/ExprHandler/ScalarHandler.php +++ b/src/Analyser/ExprHandler/ScalarHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index 3a6d490730c..286d139e667 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -81,6 +81,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $hasYield = false; $throwPoints = []; $impurePoints = []; @@ -283,6 +284,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php index dd68417723f..13a6279a5a1 100644 --- a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php @@ -53,6 +53,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $hasYield = false; $throwPoints = []; $impurePoints = [ @@ -84,6 +85,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/TernaryHandler.php b/src/Analyser/ExprHandler/TernaryHandler.php index b1385b149b6..b26c09378e9 100644 --- a/src/Analyser/ExprHandler/TernaryHandler.php +++ b/src/Analyser/ExprHandler/TernaryHandler.php @@ -148,6 +148,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $finalScope, + beforeScope: $scope, hasYield: $ternaryCondResult->hasYield(), isAlwaysTerminating: $ternaryCondResult->isAlwaysTerminating(), throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ThrowHandler.php b/src/Analyser/ExprHandler/ThrowHandler.php index 7c283decba4..2cf4666b6bd 100644 --- a/src/Analyser/ExprHandler/ThrowHandler.php +++ b/src/Analyser/ExprHandler/ThrowHandler.php @@ -44,6 +44,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: true, throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createExplicit($scope, $scope->getType($expr->expr), $expr, false)]), diff --git a/src/Analyser/ExprHandler/UnaryMinusHandler.php b/src/Analyser/ExprHandler/UnaryMinusHandler.php index 0da8e3d46a4..d97068c3acb 100644 --- a/src/Analyser/ExprHandler/UnaryMinusHandler.php +++ b/src/Analyser/ExprHandler/UnaryMinusHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), + beforeScope: $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/UnaryPlusHandler.php b/src/Analyser/ExprHandler/UnaryPlusHandler.php index 3f2c6bcbbf9..e2450452e31 100644 --- a/src/Analyser/ExprHandler/UnaryPlusHandler.php +++ b/src/Analyser/ExprHandler/UnaryPlusHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), + beforeScope: $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/VariableHandler.php b/src/Analyser/ExprHandler/VariableHandler.php index f2c6f414207..3fdbdd0544a 100644 --- a/src/Analyser/ExprHandler/VariableHandler.php +++ b/src/Analyser/ExprHandler/VariableHandler.php @@ -78,6 +78,7 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $hasYield = false; $throwPoints = []; $impurePoints = []; @@ -96,6 +97,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( $scope, + $beforeScope, $hasYield, $isAlwaysTerminating, $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php index 8e26801f1e0..7a75e944ebc 100644 --- a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php @@ -45,12 +45,14 @@ public function processExpr( ExpressionContext $context, ): ExpressionResult { + $beforeScope = $scope; $innerExpr = $expr->getExpr(); $innerResult = $nodeScopeResolver->processExprNode($stmt, $innerExpr, $scope, $storage, $nodeCallback, $context); $scope = $innerResult->getScope(); return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $innerResult->hasYield(), isAlwaysTerminating: $innerResult->isAlwaysTerminating(), throwPoints: $innerResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php index 071f56be856..02d15c413b8 100644 --- a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php index 4b91658cc82..8f3aaf827c2 100644 --- a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php @@ -38,6 +38,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $throwPoints = []; $impurePoints = []; $hasYield = false; @@ -53,6 +54,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php index 241122ccf84..4d5f0e54a82 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php index 040732cfac3..5b5b59ee994 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php index 539ecc7a3b1..03f846910c4 100644 --- a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php index 3ce468022a9..dddc6e216c9 100644 --- a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php @@ -38,6 +38,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $throwPoints = []; $impurePoints = []; $hasYield = false; @@ -53,6 +54,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php index c94173c354f..39b370ed581 100644 --- a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php @@ -39,6 +39,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->getVar(), $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); $scope = $varResult->getScope(); $hasYield = $varResult->hasYield(); @@ -56,6 +57,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php index e916eb9ec69..0b7999acb50 100644 --- a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php index ea08496e209..9f36aee736c 100644 --- a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php @@ -47,6 +47,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php index 5c986631ba1..7c7f2847d29 100644 --- a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php @@ -44,6 +44,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php index a8ffc98a0bb..e604dece482 100644 --- a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php @@ -44,6 +44,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php index 0eef43563b0..34e7dbf25b7 100644 --- a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php @@ -39,6 +39,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $throwPoints = []; $impurePoints = []; $hasYield = false; @@ -62,6 +63,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php index 53766b68323..26b05213a50 100644 --- a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php index 995219a4f05..1246378b936 100644 --- a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/YieldFromHandler.php b/src/Analyser/ExprHandler/YieldFromHandler.php index 9e148f388f7..089ce51b630 100644 --- a/src/Analyser/ExprHandler/YieldFromHandler.php +++ b/src/Analyser/ExprHandler/YieldFromHandler.php @@ -54,11 +54,13 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: true, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createImplicit($scope, $expr)]), diff --git a/src/Analyser/ExprHandler/YieldHandler.php b/src/Analyser/ExprHandler/YieldHandler.php index 583907a17b6..76356bfbf7b 100644 --- a/src/Analyser/ExprHandler/YieldHandler.php +++ b/src/Analyser/ExprHandler/YieldHandler.php @@ -59,6 +59,7 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + $beforeScope = $scope; $throwPoints = [ InternalThrowPoint::createImplicit($scope, $expr), ]; @@ -89,6 +90,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, + beforeScope: $beforeScope, hasYield: true, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 42e49e004aa..c75c3b3ac33 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -26,6 +26,7 @@ final class ExpressionResult */ public function __construct( private MutatingScope $scope, + private MutatingScope $beforeScope, private bool $hasYield, private bool $isAlwaysTerminating, private array $throwPoints, @@ -43,6 +44,11 @@ public function getScope(): MutatingScope return $this->scope; } + public function getBeforeScope(): MutatingScope + { + return $this->beforeScope; + } + public function hasYield(): bool { return $this->hasYield; diff --git a/src/Analyser/ExpressionResultFactory.php b/src/Analyser/ExpressionResultFactory.php index f724e6fdcb4..e065862b20a 100644 --- a/src/Analyser/ExpressionResultFactory.php +++ b/src/Analyser/ExpressionResultFactory.php @@ -13,6 +13,7 @@ interface ExpressionResultFactory */ public function create( MutatingScope $scope, + MutatingScope $beforeScope, bool $hasYield, bool $isAlwaysTerminating, array $throwPoints, diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 77230c619f3..8c4ee8058b6 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2765,11 +2765,12 @@ public function processExprNode( if ($expr instanceof List_) { // only in assign and foreach, processed elsewhere - return $this->expressionResultFactory->create($scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []); + return $this->expressionResultFactory->create($scope, beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []); } return $this->expressionResultFactory->create( $scope, + beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], @@ -3136,7 +3137,7 @@ public function processArrowFunctionNode( $this->callNodeCallback($nodeCallback, new InArrowFunctionNode($arrowFunctionType, $expr), $arrowFunctionScope, $storage); $exprResult = $this->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $storage, $nodeCallback, ExpressionContext::createTopLevel()); - return $this->expressionResultFactory->create($scope, false, $exprResult->isAlwaysTerminating(), $exprResult->getThrowPoints(), $exprResult->getImpurePoints()); + return $this->expressionResultFactory->create($scope, beforeScope: $scope, hasYield: false, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), impurePoints: $exprResult->getImpurePoints()); } /** @@ -3839,7 +3840,7 @@ public function processArgs( } // not storing this, it's scope after processing all args - return $this->expressionResultFactory->create($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($scope, $scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } /** @@ -3976,7 +3977,7 @@ public function processVirtualAssign(MutatingScope $scope, ExpressionResultStora $assignedExpr, new VirtualAssignNodeCallback($nodeCallback), ExpressionContext::createDeep(), - fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), + fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), false, ); } From b8157b6378578008cd8030c770b34e563c25eb09 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Jun 2026 12:36:11 +0200 Subject: [PATCH 03/22] ExpressionResult - add Expr --- .../ExprHandler/ArrayDimFetchHandler.php | 6 +-- src/Analyser/ExprHandler/ArrayHandler.php | 1 + .../ExprHandler/ArrowFunctionHandler.php | 1 + src/Analyser/ExprHandler/AssignHandler.php | 12 +++--- src/Analyser/ExprHandler/AssignOpHandler.php | 4 +- src/Analyser/ExprHandler/BinaryOpHandler.php | 3 +- .../ExprHandler/BitwiseNotHandler.php | 1 + .../ExprHandler/BooleanAndHandler.php | 1 + .../ExprHandler/BooleanNotHandler.php | 3 +- src/Analyser/ExprHandler/BooleanOrHandler.php | 1 + src/Analyser/ExprHandler/CastHandler.php | 3 +- .../ExprHandler/CastStringHandler.php | 3 +- .../ExprHandler/ClassConstFetchHandler.php | 3 +- src/Analyser/ExprHandler/CloneHandler.php | 1 + src/Analyser/ExprHandler/ClosureHandler.php | 1 + src/Analyser/ExprHandler/CoalesceHandler.php | 3 +- .../ExprHandler/ConstFetchHandler.php | 3 +- src/Analyser/ExprHandler/EmptyHandler.php | 3 +- .../ExprHandler/ErrorSuppressHandler.php | 1 + src/Analyser/ExprHandler/EvalHandler.php | 1 + src/Analyser/ExprHandler/ExitHandler.php | 1 + src/Analyser/ExprHandler/FuncCallHandler.php | 3 +- .../Helper/ImplicitToStringCallHelper.php | 2 + src/Analyser/ExprHandler/IncludeHandler.php | 1 + .../ExprHandler/InstanceofHandler.php | 3 +- .../ExprHandler/InterpolatedStringHandler.php | 1 + src/Analyser/ExprHandler/IssetHandler.php | 3 +- src/Analyser/ExprHandler/MatchHandler.php | 1 + .../ExprHandler/MethodCallHandler.php | 6 +-- src/Analyser/ExprHandler/NewHandler.php | 1 + .../ExprHandler/NullsafeMethodCallHandler.php | 3 +- .../NullsafePropertyFetchHandler.php | 3 +- src/Analyser/ExprHandler/PipeHandler.php | 1 + src/Analyser/ExprHandler/PostDecHandler.php | 1 + src/Analyser/ExprHandler/PostIncHandler.php | 1 + src/Analyser/ExprHandler/PreDecHandler.php | 1 + src/Analyser/ExprHandler/PreIncHandler.php | 1 + src/Analyser/ExprHandler/PrintHandler.php | 1 + .../ExprHandler/PropertyFetchHandler.php | 3 +- src/Analyser/ExprHandler/ScalarHandler.php | 1 + .../ExprHandler/StaticCallHandler.php | 3 +- .../StaticPropertyFetchHandler.php | 3 +- src/Analyser/ExprHandler/TernaryHandler.php | 3 +- src/Analyser/ExprHandler/ThrowHandler.php | 1 + .../ExprHandler/UnaryMinusHandler.php | 1 + src/Analyser/ExprHandler/UnaryPlusHandler.php | 1 + src/Analyser/ExprHandler/VariableHandler.php | 1 + .../Virtual/AlwaysRememberedExprHandler.php | 3 +- .../Virtual/ExistingArrayDimFetchHandler.php | 1 + .../Virtual/FunctionCallableNodeHandler.php | 1 + .../Virtual/GetIterableKeyTypeExprHandler.php | 1 + .../GetIterableValueTypeExprHandler.php | 1 + .../Virtual/GetOffsetValueTypeExprHandler.php | 1 + .../InstantiationCallableNodeHandler.php | 1 + .../Virtual/MethodCallableNodeHandler.php | 1 + .../Virtual/NativeTypeExprHandler.php | 1 + .../OriginalPropertyTypeExprHandler.php | 1 + .../SetExistingOffsetValueTypeExprHandler.php | 1 + .../Virtual/SetOffsetValueTypeExprHandler.php | 1 + .../StaticMethodCallableNodeHandler.php | 1 + .../ExprHandler/Virtual/TypeExprHandler.php | 1 + .../Virtual/UnsetOffsetExprHandler.php | 1 + src/Analyser/ExprHandler/YieldFromHandler.php | 1 + src/Analyser/ExprHandler/YieldHandler.php | 1 + src/Analyser/ExpressionResult.php | 40 +++++++++++-------- src/Analyser/ExpressionResultFactory.php | 3 ++ src/Analyser/NodeScopeResolver.php | 15 ++++--- 67 files changed, 106 insertions(+), 77 deletions(-) diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index 59ea71f1e31..0a0198cf3c2 100644 --- a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php @@ -89,12 +89,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), impurePoints: $varResult->getImpurePoints(), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } @@ -119,12 +118,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $dimResult->hasYield() || $varResult->hasYield(), isAlwaysTerminating: $dimResult->isAlwaysTerminating() || $varResult->isAlwaysTerminating(), throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index 78eb2b5491d..a8e58b932a4 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -104,6 +104,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ArrowFunctionHandler.php b/src/Analyser/ExprHandler/ArrowFunctionHandler.php index b98fc9c85de..5390ee8e212 100644 --- a/src/Analyser/ExprHandler/ArrowFunctionHandler.php +++ b/src/Analyser/ExprHandler/ArrowFunctionHandler.php @@ -46,6 +46,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $result->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $result->hasYield(), isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index a9248dc6d3a..70caac5134b 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -342,7 +342,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $scope = $scope->exitExpressionAssign($expr->expr); } - return $this->expressionResultFactory->create($scope, $beforeScope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($scope, $beforeScope, $expr->expr, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); }, true, ); @@ -387,12 +387,11 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), throwPoints: $result->getThrowPoints(), impurePoints: $result->getImpurePoints(), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } @@ -935,16 +934,17 @@ public function processAssignVar( } else { $dimExpr = $arrayItem->key; } + $getOffsetValueTypeExpr = new GetOffsetValueTypeExpr($assignedExpr, $dimExpr); $result = $this->processAssignVar( $nodeScopeResolver, $scope, $storage, $stmt, $arrayItem->value, - new GetOffsetValueTypeExpr($assignedExpr, $dimExpr), + $getOffsetValueTypeExpr, $nodeCallback, $context, - fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), + fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, beforeScope: $scope, expr: $getOffsetValueTypeExpr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), $enterExpressionAssign, ); $scope = $result->getScope(); @@ -1036,7 +1036,7 @@ public function processAssignVar( } // stored where processAssignVar is called - return $this->expressionResultFactory->create($scope, $beforeScope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($scope, $beforeScope, $var, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } private function createArrayDimFetchConditionalExpressionHolder( diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index 5d0d4c8f842..ff70d04dbe5 100644 --- a/src/Analyser/ExprHandler/AssignOpHandler.php +++ b/src/Analyser/ExprHandler/AssignOpHandler.php @@ -80,6 +80,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto return $this->expressionResultFactory->create( $exprResult->getScope()->mergeWith($originalScope), $originalScope, + $expr->expr, $exprResult->hasYield(), $isAlwaysTerminating, $exprResult->getThrowPoints(), @@ -112,12 +113,11 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $assignResult->hasYield(), isAlwaysTerminating: $assignResult->isAlwaysTerminating(), throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/BinaryOpHandler.php b/src/Analyser/ExprHandler/BinaryOpHandler.php index 70d7841012a..c3d686ba134 100644 --- a/src/Analyser/ExprHandler/BinaryOpHandler.php +++ b/src/Analyser/ExprHandler/BinaryOpHandler.php @@ -107,12 +107,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating() || $rightResult->isAlwaysTerminating(), throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/BitwiseNotHandler.php b/src/Analyser/ExprHandler/BitwiseNotHandler.php index ae9ec208bd5..4b6b6667823 100644 --- a/src/Analyser/ExprHandler/BitwiseNotHandler.php +++ b/src/Analyser/ExprHandler/BitwiseNotHandler.php @@ -46,6 +46,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/BooleanAndHandler.php b/src/Analyser/ExprHandler/BooleanAndHandler.php index 26be561a74e..1265071cafa 100644 --- a/src/Analyser/ExprHandler/BooleanAndHandler.php +++ b/src/Analyser/ExprHandler/BooleanAndHandler.php @@ -266,6 +266,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $leftMergedWithRightScope, beforeScope: $scope, + expr: $expr, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating(), throwPoints: array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), diff --git a/src/Analyser/ExprHandler/BooleanNotHandler.php b/src/Analyser/ExprHandler/BooleanNotHandler.php index 08091ce1dc0..59cb5a986c1 100644 --- a/src/Analyser/ExprHandler/BooleanNotHandler.php +++ b/src/Analyser/ExprHandler/BooleanNotHandler.php @@ -46,12 +46,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), impurePoints: $exprResult->getImpurePoints(), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/BooleanOrHandler.php b/src/Analyser/ExprHandler/BooleanOrHandler.php index 46e8bc28631..9b1d34d1cc2 100644 --- a/src/Analyser/ExprHandler/BooleanOrHandler.php +++ b/src/Analyser/ExprHandler/BooleanOrHandler.php @@ -308,6 +308,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $leftMergedWithRightScope, beforeScope: $scope, + expr: $expr, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating(), throwPoints: array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()), diff --git a/src/Analyser/ExprHandler/CastHandler.php b/src/Analyser/ExprHandler/CastHandler.php index 99085f9b800..6877fdd5b3e 100644 --- a/src/Analyser/ExprHandler/CastHandler.php +++ b/src/Analyser/ExprHandler/CastHandler.php @@ -55,12 +55,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), impurePoints: $exprResult->getImpurePoints(), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/CastStringHandler.php b/src/Analyser/ExprHandler/CastStringHandler.php index 32f31ec60a1..bb13a0e2817 100644 --- a/src/Analyser/ExprHandler/CastStringHandler.php +++ b/src/Analyser/ExprHandler/CastStringHandler.php @@ -60,12 +60,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/ClassConstFetchHandler.php b/src/Analyser/ExprHandler/ClassConstFetchHandler.php index 3283d8f4153..fe989f332c9 100644 --- a/src/Analyser/ExprHandler/ClassConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ClassConstFetchHandler.php @@ -89,12 +89,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/CloneHandler.php b/src/Analyser/ExprHandler/CloneHandler.php index ee1951e68d5..bc707347411 100644 --- a/src/Analyser/ExprHandler/CloneHandler.php +++ b/src/Analyser/ExprHandler/CloneHandler.php @@ -46,6 +46,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/ClosureHandler.php b/src/Analyser/ExprHandler/ClosureHandler.php index e0e742e812d..b107c5843c6 100644 --- a/src/Analyser/ExprHandler/ClosureHandler.php +++ b/src/Analyser/ExprHandler/ClosureHandler.php @@ -46,6 +46,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $processClosureResult->applyByRefUseScope($processClosureResult->getScope()), beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index 562a4f9075c..f47554f2811 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -135,12 +135,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $condResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $condResult->isAlwaysTerminating(), throwPoints: array_merge($condResult->getThrowPoints(), $rightResult->getThrowPoints()), impurePoints: array_merge($condResult->getImpurePoints(), $rightResult->getImpurePoints()), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/ConstFetchHandler.php b/src/Analyser/ExprHandler/ConstFetchHandler.php index 23510dbbaba..17f429322e0 100644 --- a/src/Analyser/ExprHandler/ConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ConstFetchHandler.php @@ -51,12 +51,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: [], - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/EmptyHandler.php b/src/Analyser/ExprHandler/EmptyHandler.php index d5f932b2b14..54e9c397fa1 100644 --- a/src/Analyser/ExprHandler/EmptyHandler.php +++ b/src/Analyser/ExprHandler/EmptyHandler.php @@ -98,12 +98,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), impurePoints: $exprResult->getImpurePoints(), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/ErrorSuppressHandler.php b/src/Analyser/ExprHandler/ErrorSuppressHandler.php index 700d260261a..ca006ebcedb 100644 --- a/src/Analyser/ExprHandler/ErrorSuppressHandler.php +++ b/src/Analyser/ExprHandler/ErrorSuppressHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/EvalHandler.php b/src/Analyser/ExprHandler/EvalHandler.php index d1b7160701f..9bbbbe9fbd2 100644 --- a/src/Analyser/ExprHandler/EvalHandler.php +++ b/src/Analyser/ExprHandler/EvalHandler.php @@ -53,6 +53,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createImplicit($scope, $expr)]), diff --git a/src/Analyser/ExprHandler/ExitHandler.php b/src/Analyser/ExprHandler/ExitHandler.php index 0cba81da42c..7c1029c14e2 100644 --- a/src/Analyser/ExprHandler/ExitHandler.php +++ b/src/Analyser/ExprHandler/ExitHandler.php @@ -60,6 +60,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: true, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index 673d0159ba9..a36f1ba1c94 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -577,12 +577,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php b/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php index 9303995b032..58ed6c39ac6 100644 --- a/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php +++ b/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php @@ -40,6 +40,7 @@ public function processImplicitToStringCall(Expr $expr, MutatingScope $scope): E return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], @@ -73,6 +74,7 @@ public function processImplicitToStringCall(Expr $expr, MutatingScope $scope): E return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/IncludeHandler.php b/src/Analyser/ExprHandler/IncludeHandler.php index 44df8e73801..2a0dd18b37f 100644 --- a/src/Analyser/ExprHandler/IncludeHandler.php +++ b/src/Analyser/ExprHandler/IncludeHandler.php @@ -55,6 +55,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createImplicit($scope, $expr)]), diff --git a/src/Analyser/ExprHandler/InstanceofHandler.php b/src/Analyser/ExprHandler/InstanceofHandler.php index 077e965ffdc..b5288696912 100644 --- a/src/Analyser/ExprHandler/InstanceofHandler.php +++ b/src/Analyser/ExprHandler/InstanceofHandler.php @@ -69,12 +69,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/InterpolatedStringHandler.php b/src/Analyser/ExprHandler/InterpolatedStringHandler.php index df25ce12114..cdf2bdba301 100644 --- a/src/Analyser/ExprHandler/InterpolatedStringHandler.php +++ b/src/Analyser/ExprHandler/InterpolatedStringHandler.php @@ -71,6 +71,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/IssetHandler.php b/src/Analyser/ExprHandler/IssetHandler.php index 001ee84f127..9fc6c135a1c 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.php @@ -391,12 +391,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/MatchHandler.php b/src/Analyser/ExprHandler/MatchHandler.php index 42d69ee30c7..60ac5f5b88b 100644 --- a/src/Analyser/ExprHandler/MatchHandler.php +++ b/src/Analyser/ExprHandler/MatchHandler.php @@ -508,6 +508,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index 49b5b113759..cd521235237 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -197,12 +197,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $result = $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); $calledOnType = $originalScope->getType($expr->var); @@ -226,12 +225,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), throwPoints: $result->getThrowPoints(), impurePoints: $result->getImpurePoints(), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } } diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index da9eddc93a9..a70924f2118 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -221,6 +221,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php index 05a94ad2668..215c1d6b497 100644 --- a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php +++ b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php @@ -121,12 +121,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, throwPoints: $exprResult->getThrowPoints(), impurePoints: $exprResult->getImpurePoints(), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php index d9df195fd37..b5d253da2b0 100644 --- a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php @@ -100,12 +100,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, throwPoints: $exprResult->getThrowPoints(), impurePoints: $exprResult->getImpurePoints(), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/PipeHandler.php b/src/Analyser/ExprHandler/PipeHandler.php index e6d010e2bf4..d3bbbc90413 100644 --- a/src/Analyser/ExprHandler/PipeHandler.php +++ b/src/Analyser/ExprHandler/PipeHandler.php @@ -92,6 +92,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $callResult->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $callResult->hasYield(), isAlwaysTerminating: $callResult->isAlwaysTerminating(), throwPoints: $callResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PostDecHandler.php b/src/Analyser/ExprHandler/PostDecHandler.php index c28a4a0175e..ecdf3bd84d8 100644 --- a/src/Analyser/ExprHandler/PostDecHandler.php +++ b/src/Analyser/ExprHandler/PostDecHandler.php @@ -50,6 +50,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, )->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PostIncHandler.php b/src/Analyser/ExprHandler/PostIncHandler.php index f54fa7d4e0a..9a68af90336 100644 --- a/src/Analyser/ExprHandler/PostIncHandler.php +++ b/src/Analyser/ExprHandler/PostIncHandler.php @@ -50,6 +50,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, )->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PreDecHandler.php b/src/Analyser/ExprHandler/PreDecHandler.php index 344dde7a6ad..6569fde8c10 100644 --- a/src/Analyser/ExprHandler/PreDecHandler.php +++ b/src/Analyser/ExprHandler/PreDecHandler.php @@ -113,6 +113,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, )->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PreIncHandler.php b/src/Analyser/ExprHandler/PreIncHandler.php index 9e6d026f101..7d4be597076 100644 --- a/src/Analyser/ExprHandler/PreIncHandler.php +++ b/src/Analyser/ExprHandler/PreIncHandler.php @@ -114,6 +114,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, )->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PrintHandler.php b/src/Analyser/ExprHandler/PrintHandler.php index d495f8b63ca..cd6a90aee17 100644 --- a/src/Analyser/ExprHandler/PrintHandler.php +++ b/src/Analyser/ExprHandler/PrintHandler.php @@ -63,6 +63,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/PropertyFetchHandler.php b/src/Analyser/ExprHandler/PropertyFetchHandler.php index 06d78067198..61152971d82 100644 --- a/src/Analyser/ExprHandler/PropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/PropertyFetchHandler.php @@ -87,12 +87,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/ScalarHandler.php b/src/Analyser/ExprHandler/ScalarHandler.php index ade6dc84023..9b4de986801 100644 --- a/src/Analyser/ExprHandler/ScalarHandler.php +++ b/src/Analyser/ExprHandler/ScalarHandler.php @@ -46,6 +46,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index 286d139e667..35b391d6e0f 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -285,12 +285,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php index 13a6279a5a1..c6d61ddd866 100644 --- a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php @@ -86,12 +86,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/TernaryHandler.php b/src/Analyser/ExprHandler/TernaryHandler.php index b26c09378e9..101ffe45330 100644 --- a/src/Analyser/ExprHandler/TernaryHandler.php +++ b/src/Analyser/ExprHandler/TernaryHandler.php @@ -149,12 +149,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $finalScope, beforeScope: $scope, + expr: $expr, hasYield: $ternaryCondResult->hasYield(), isAlwaysTerminating: $ternaryCondResult->isAlwaysTerminating(), throwPoints: $throwPoints, impurePoints: $impurePoints, - truthyScopeCallback: static fn (): MutatingScope => $finalScope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $finalScope->filterByFalseyValue($expr), ); } diff --git a/src/Analyser/ExprHandler/ThrowHandler.php b/src/Analyser/ExprHandler/ThrowHandler.php index 2cf4666b6bd..05bda29ec2d 100644 --- a/src/Analyser/ExprHandler/ThrowHandler.php +++ b/src/Analyser/ExprHandler/ThrowHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: true, throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createExplicit($scope, $scope->getType($expr->expr), $expr, false)]), diff --git a/src/Analyser/ExprHandler/UnaryMinusHandler.php b/src/Analyser/ExprHandler/UnaryMinusHandler.php index d97068c3acb..ea67e7dabc4 100644 --- a/src/Analyser/ExprHandler/UnaryMinusHandler.php +++ b/src/Analyser/ExprHandler/UnaryMinusHandler.php @@ -46,6 +46,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/UnaryPlusHandler.php b/src/Analyser/ExprHandler/UnaryPlusHandler.php index e2450452e31..6ec1abe38fc 100644 --- a/src/Analyser/ExprHandler/UnaryPlusHandler.php +++ b/src/Analyser/ExprHandler/UnaryPlusHandler.php @@ -46,6 +46,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $exprResult->getScope(), beforeScope: $scope, + expr: $expr, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/VariableHandler.php b/src/Analyser/ExprHandler/VariableHandler.php index 3fdbdd0544a..6082dea55f2 100644 --- a/src/Analyser/ExprHandler/VariableHandler.php +++ b/src/Analyser/ExprHandler/VariableHandler.php @@ -98,6 +98,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, $beforeScope, + $expr, $hasYield, $isAlwaysTerminating, $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php index 7a75e944ebc..99d6d9925e8 100644 --- a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php @@ -53,12 +53,11 @@ public function processExpr( return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $innerResult->hasYield(), isAlwaysTerminating: $innerResult->isAlwaysTerminating(), throwPoints: $innerResult->getThrowPoints(), impurePoints: $innerResult->getImpurePoints(), - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($innerExpr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($innerExpr), ); } diff --git a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php index 02d15c413b8..411d2ee8d65 100644 --- a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php @@ -43,6 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php index 8f3aaf827c2..e56509e627e 100644 --- a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php @@ -55,6 +55,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php index 4d5f0e54a82..127ef939539 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php @@ -43,6 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php index 5b5b59ee994..56b7b9be00e 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php @@ -43,6 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php index 03f846910c4..ed0b5da45c5 100644 --- a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php @@ -43,6 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php index dddc6e216c9..937b6618d85 100644 --- a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php @@ -55,6 +55,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php index 39b370ed581..28492541bce 100644 --- a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php @@ -58,6 +58,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php index 0b7999acb50..852d00b14cd 100644 --- a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php @@ -43,6 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php index 9f36aee736c..2621d242cc7 100644 --- a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php @@ -48,6 +48,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php index 7c7f2847d29..11487e514f2 100644 --- a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php index e604dece482..c85440094b8 100644 --- a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php index 34e7dbf25b7..b12d7e120e5 100644 --- a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php @@ -64,6 +64,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php index 26b05213a50..6ca636fe081 100644 --- a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php @@ -43,6 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php index 1246378b936..d414e648fd2 100644 --- a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php @@ -43,6 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/YieldFromHandler.php b/src/Analyser/ExprHandler/YieldFromHandler.php index 089ce51b630..1aac8244af7 100644 --- a/src/Analyser/ExprHandler/YieldFromHandler.php +++ b/src/Analyser/ExprHandler/YieldFromHandler.php @@ -61,6 +61,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: true, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createImplicit($scope, $expr)]), diff --git a/src/Analyser/ExprHandler/YieldHandler.php b/src/Analyser/ExprHandler/YieldHandler.php index 76356bfbf7b..07abbc7e6ee 100644 --- a/src/Analyser/ExprHandler/YieldHandler.php +++ b/src/Analyser/ExprHandler/YieldHandler.php @@ -91,6 +91,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, + expr: $expr, hasYield: true, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index c75c3b3ac33..1e833cfcc0d 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -2,7 +2,9 @@ namespace PHPStan\Analyser; +use PhpParser\Node\Expr; use PHPStan\DependencyInjection\GenerateFactory; +use PHPStan\Type\Type; #[GenerateFactory(interface: ExpressionResultFactory::class)] final class ExpressionResult @@ -27,6 +29,7 @@ final class ExpressionResult public function __construct( private MutatingScope $scope, private MutatingScope $beforeScope, + private Expr $expr, private bool $hasYield, private bool $isAlwaysTerminating, private array $throwPoints, @@ -44,11 +47,6 @@ public function getScope(): MutatingScope return $this->scope; } - public function getBeforeScope(): MutatingScope - { - return $this->beforeScope; - } - public function hasYield(): bool { return $this->hasYield; @@ -72,32 +70,30 @@ public function getImpurePoints(): array public function getTruthyScope(): MutatingScope { - if ($this->truthyScopeCallback === null) { - return $this->scope; - } - if ($this->truthyScope !== null) { return $this->truthyScope; } + if ($this->truthyScopeCallback === null) { + return $this->truthyScope = $this->scope->filterByTruthyValue($this->expr); + } + $callback = $this->truthyScopeCallback; - $this->truthyScope = $callback(); - return $this->truthyScope; + return $this->truthyScope = $callback(); } public function getFalseyScope(): MutatingScope { - if ($this->falseyScopeCallback === null) { - return $this->scope; - } - if ($this->falseyScope !== null) { return $this->falseyScope; } + if ($this->falseyScopeCallback === null) { + return $this->falseyScope = $this->scope->filterByFalseyValue($this->expr); + } + $callback = $this->falseyScopeCallback; - $this->falseyScope = $callback(); - return $this->falseyScope; + return $this->falseyScope = $callback(); } public function isAlwaysTerminating(): bool @@ -105,4 +101,14 @@ public function isAlwaysTerminating(): bool return $this->isAlwaysTerminating; } + public function getType(): Type + { + return $this->beforeScope->getType($this->expr); + } + + public function getNativeType(): Type + { + return $this->beforeScope->getNativeType($this->expr); + } + } diff --git a/src/Analyser/ExpressionResultFactory.php b/src/Analyser/ExpressionResultFactory.php index e065862b20a..255d6258fba 100644 --- a/src/Analyser/ExpressionResultFactory.php +++ b/src/Analyser/ExpressionResultFactory.php @@ -2,6 +2,8 @@ namespace PHPStan\Analyser; +use PhpParser\Node\Expr; + interface ExpressionResultFactory { @@ -14,6 +16,7 @@ interface ExpressionResultFactory public function create( MutatingScope $scope, MutatingScope $beforeScope, + Expr $expr, bool $hasYield, bool $isAlwaysTerminating, array $throwPoints, diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8c4ee8058b6..f9181659c5c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1302,9 +1302,9 @@ public function processStmtNode( $this->callNodeCallback($nodeCallback, $stmt->type, $scope, $storage); } } 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, $storage, $nodeCallback, ExpressionContext::createDeep()); + $conditionType = ($this->treatPhpDocTypesAsCertain ? $condResult->getType() : $condResult->getNativeType())->toBoolean(); + $ifAlwaysTrue = $conditionType->isTrue()->yes(); $exitPoints = []; $throwPoints = $overridingThrowPoints ?? $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); @@ -2765,18 +2765,17 @@ public function processExprNode( if ($expr instanceof List_) { // only in assign and foreach, processed elsewhere - return $this->expressionResultFactory->create($scope, beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []); + return $this->expressionResultFactory->create($scope, beforeScope: $scope, expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []); } return $this->expressionResultFactory->create( $scope, beforeScope: $scope, + expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: [], - truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr), ); } @@ -3137,7 +3136,7 @@ public function processArrowFunctionNode( $this->callNodeCallback($nodeCallback, new InArrowFunctionNode($arrowFunctionType, $expr), $arrowFunctionScope, $storage); $exprResult = $this->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $storage, $nodeCallback, ExpressionContext::createTopLevel()); - return $this->expressionResultFactory->create($scope, beforeScope: $scope, hasYield: false, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), impurePoints: $exprResult->getImpurePoints()); + return $this->expressionResultFactory->create($scope, beforeScope: $scope, expr: $expr, hasYield: false, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), impurePoints: $exprResult->getImpurePoints()); } /** @@ -3840,7 +3839,7 @@ public function processArgs( } // not storing this, it's scope after processing all args - return $this->expressionResultFactory->create($scope, $scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($scope, $scope, $callLike, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } /** @@ -3977,7 +3976,7 @@ public function processVirtualAssign(MutatingScope $scope, ExpressionResultStora $assignedExpr, new VirtualAssignNodeCallback($nodeCallback), ExpressionContext::createDeep(), - fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, beforeScope: $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), + fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($scope, beforeScope: $scope, expr: $assignedExpr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), false, ); } From c8b3bda333ab026e8e2985d1a5d4cd138b19df84 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Jun 2026 20:16:10 +0200 Subject: [PATCH 04/22] Store ExpressionResult instead of before-Scope --- src/Analyser/ExprHandler/AssignHandler.php | 34 +++++++++++--- src/Analyser/ExprHandler/AssignOpHandler.php | 4 -- src/Analyser/ExpressionResult.php | 5 +++ src/Analyser/ExpressionResultStorage.php | 22 +++++----- ...equest.php => ExpressionResultRequest.php} | 5 +-- src/Analyser/Fiber/FiberNodeScopeResolver.php | 44 +++++++++++++------ src/Analyser/Fiber/FiberScope.php | 42 +++++++++++++----- src/Analyser/NodeScopeResolver.php | 42 +++++++++++++----- 8 files changed, 135 insertions(+), 63 deletions(-) rename src/Analyser/Fiber/{BeforeScopeForExprRequest.php => ExpressionResultRequest.php} (61%) diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 70caac5134b..143c52da656 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -330,7 +330,6 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto ); } - $nodeScopeResolver->storeBeforeScope($storage, $expr, $scope); $result = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -413,6 +412,15 @@ public function processAssignVar( ): ExpressionResult { $beforeScope = $scope; + $nodeScopeResolver->storeExpressionResult($storage, $var, $this->expressionResultFactory->create( + $scope, + beforeScope: $scope, + expr: $var, + hasYield: false, + isAlwaysTerminating: false, + throwPoints: [], + impurePoints: [], + )); $nodeScopeResolver->callNodeCallback($nodeCallback, $var, $enterExpressionAssign ? $scope->enterExpressionAssign($var) : $scope, $storage); $hasYield = false; $throwPoints = []; @@ -420,7 +428,6 @@ public function processAssignVar( $isAlwaysTerminating = false; $isAssignOp = $assignedExpr instanceof Expr\AssignOp && !$enterExpressionAssign; if ($var instanceof Variable) { - $nodeScopeResolver->storeBeforeScope($storage, $var, $scope); $result = $processExprCallback($scope); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -592,7 +599,15 @@ public function processAssignVar( if ($dimExpr === null) { $offsetTypes[] = [null, $dimFetch]; $offsetNativeTypes[] = [null, $dimFetch]; - $nodeScopeResolver->storeBeforeScope($storage, $dimFetch, $scope); + $nodeScopeResolver->storeExpressionResult($storage, $dimFetch, $this->expressionResultFactory->create( + $scope, + beforeScope: $scope, + expr: $dimFetch, + hasYield: false, + isAlwaysTerminating: false, + throwPoints: [], + impurePoints: [], + )); } else { $offsetTypes[] = [$scope->getType($dimExpr), $dimFetch]; @@ -601,7 +616,15 @@ public function processAssignVar( if ($enterExpressionAssign) { $scope->enterExpressionAssign($dimExpr); } - $nodeScopeResolver->storeBeforeScope($storage, $dimFetch, $scope); + $nodeScopeResolver->storeExpressionResult($storage, $dimFetch, $this->expressionResultFactory->create( + $scope, + beforeScope: $scope, + expr: $dimFetch, + hasYield: false, + isAlwaysTerminating: false, + throwPoints: [], + impurePoints: [], + )); $result = $nodeScopeResolver->processExprNode($stmt, $dimExpr, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -723,7 +746,6 @@ public function processAssignVar( )->getThrowPoints()); } } elseif ($var instanceof PropertyFetch) { - $nodeScopeResolver->storeBeforeScope($storage, $var, $scope); $objectResult = $nodeScopeResolver->processExprNode($stmt, $var->var, $scope, $storage, $nodeCallback, $context); $hasYield = $objectResult->hasYield(); $throwPoints = $objectResult->getThrowPoints(); @@ -836,7 +858,6 @@ public function processAssignVar( } } elseif ($var instanceof Expr\StaticPropertyFetch) { - $nodeScopeResolver->storeBeforeScope($storage, $var, $scope); if ($var->class instanceof Node\Name) { $propertyHolderType = $scope->resolveTypeByName($var->class); } else { @@ -902,7 +923,6 @@ public function processAssignVar( $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); } } elseif ($var instanceof List_) { - $nodeScopeResolver->storeBeforeScope($storage, $var, $scope); $result = $processExprCallback($scope); $hasYield = $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index ff70d04dbe5..5ebcff7b679 100644 --- a/src/Analyser/ExprHandler/AssignOpHandler.php +++ b/src/Analyser/ExprHandler/AssignOpHandler.php @@ -75,7 +75,6 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); if ($expr instanceof Expr\AssignOp\Coalesce) { - $nodeScopeResolver->storeBeforeScope($storage, $expr, $originalScope); $isAlwaysTerminating = $exprResult->isAlwaysTerminating() && $originalScope->getType($expr->var)->isNull()->yes(); return $this->expressionResultFactory->create( $exprResult->getScope()->mergeWith($originalScope), @@ -92,9 +91,6 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto }, $expr instanceof Expr\AssignOp\Coalesce, ); - if (!$expr instanceof Expr\AssignOp\Coalesce) { - $nodeScopeResolver->storeBeforeScope($storage, $expr, $scope); - } $scope = $assignResult->getScope(); $throwPoints = $assignResult->getThrowPoints(); $impurePoints = $assignResult->getImpurePoints(); diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 1e833cfcc0d..7669a67a2c8 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -47,6 +47,11 @@ public function getScope(): MutatingScope return $this->scope; } + public function getBeforeScope(): MutatingScope + { + return $this->beforeScope; + } + public function hasYield(): bool { return $this->hasYield; diff --git a/src/Analyser/ExpressionResultStorage.php b/src/Analyser/ExpressionResultStorage.php index d14923866c9..9fe2d2d14bd 100644 --- a/src/Analyser/ExpressionResultStorage.php +++ b/src/Analyser/ExpressionResultStorage.php @@ -5,42 +5,42 @@ use Fiber; use PhpParser\Node; use PhpParser\Node\Expr; -use PHPStan\Analyser\Fiber\BeforeScopeForExprRequest; +use PHPStan\Analyser\Fiber\ExpressionResultRequest; use PHPStan\Analyser\Fiber\ParkFiberRequest; use SplObjectStorage; final class ExpressionResultStorage { - /** @var SplObjectStorage */ - private SplObjectStorage $scopes; + /** @var SplObjectStorage */ + private SplObjectStorage $exprResults; - /** @var array, request: BeforeScopeForExprRequest}> */ + /** @var array, request: ExpressionResultRequest}> */ public array $pendingFibers = []; - /** @var list> */ + /** @var list> */ public array $parkedFibers = []; public function __construct() { - $this->scopes = new SplObjectStorage(); + $this->exprResults = new SplObjectStorage(); } public function duplicate(): self { $new = new self(); - $new->scopes->addAll($this->scopes); + $new->exprResults->addAll($this->exprResults); return $new; } - public function storeBeforeScope(Expr $expr, Scope $scope): void + public function storeExpressionResult(Expr $expr, ExpressionResult $expressionResult): void { - $this->scopes[$expr] = $scope; + $this->exprResults[$expr] = $expressionResult; } - public function findBeforeScope(Expr $expr): ?Scope + public function findExpressionResult(Expr $expr): ?ExpressionResult { - return $this->scopes[$expr] ?? null; + return $this->exprResults[$expr] ?? null; } } diff --git a/src/Analyser/Fiber/BeforeScopeForExprRequest.php b/src/Analyser/Fiber/ExpressionResultRequest.php similarity index 61% rename from src/Analyser/Fiber/BeforeScopeForExprRequest.php rename to src/Analyser/Fiber/ExpressionResultRequest.php index 0fc6ecd35cd..4f3ecabdf97 100644 --- a/src/Analyser/Fiber/BeforeScopeForExprRequest.php +++ b/src/Analyser/Fiber/ExpressionResultRequest.php @@ -3,12 +3,11 @@ namespace PHPStan\Analyser\Fiber; use PhpParser\Node\Expr; -use PHPStan\Analyser\MutatingScope; -final class BeforeScopeForExprRequest +final class ExpressionResultRequest { - public function __construct(public readonly Expr $expr, public readonly MutatingScope $scope) + public function __construct(public readonly Expr $expr, public readonly FiberScope $scope) { } diff --git a/src/Analyser/Fiber/FiberNodeScopeResolver.php b/src/Analyser/Fiber/FiberNodeScopeResolver.php index e8d160f6a1d..a0340a3781b 100644 --- a/src/Analyser/Fiber/FiberNodeScopeResolver.php +++ b/src/Analyser/Fiber/FiberNodeScopeResolver.php @@ -5,9 +5,12 @@ use Fiber; use PhpParser\Node; use PhpParser\Node\Expr; +use PHPStan\Analyser\ExpressionContext; +use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\ShouldNotHappenException; @@ -48,26 +51,26 @@ public function callNodeCallback( $this->runFiberForNodeCallback($storage, $fiber, $request); } - public function storeBeforeScope(ExpressionResultStorage $storage, Expr $expr, Scope $beforeScope): void + public function storeExpressionResult(ExpressionResultStorage $storage, Expr $expr, ExpressionResult $expressionResult): void { - $storage->storeBeforeScope($expr, $beforeScope); - $this->processPendingFibersForRequestedExpr($storage, $expr, $beforeScope); + $storage->storeExpressionResult($expr, $expressionResult); + $this->processPendingFibersForRequestedExpr($storage, $expr, $expressionResult); } /** - * @param Fiber $fiber + * @param Fiber $fiber */ private function runFiberForNodeCallback( ExpressionResultStorage $storage, Fiber $fiber, - BeforeScopeForExprRequest|ParkFiberRequest|null $request, + ExpressionResultRequest|ParkFiberRequest|null $request, ): void { while (!$fiber->isTerminated()) { - if ($request instanceof BeforeScopeForExprRequest) { - $beforeScope = $storage->findBeforeScope($request->expr); - if ($beforeScope !== null) { - $request = $fiber->resume($beforeScope); + if ($request instanceof ExpressionResultRequest) { + $expressionResult = $storage->findExpressionResult($request->expr); + if ($expressionResult !== null) { + $request = $fiber->resume($expressionResult); continue; } @@ -100,16 +103,29 @@ protected function processPendingFibers(ExpressionResultStorage $storage): void foreach ($storage->pendingFibers as $key => $pending) { $request = $pending['request']; - $beforeScope = $storage->findBeforeScope($request->expr); + $expressionResult = $storage->findExpressionResult($request->expr); - if ($beforeScope !== null) { + if ($expressionResult !== null) { throw new ShouldNotHappenException('Pending fibers at the end should be about synthetic nodes'); } unset($storage->pendingFibers[$key]); $fiber = $pending['fiber']; - $request = $fiber->resume($request->scope); + + // Process the expression with a duplicated storage so that the result + // computed from the asker's scope does not poison the real storage. + // The expression might still be processed naturally later (e.g. a loop + // condition asked about by a rule before the loop converges) and other + // fibers need to wait for that result instead of this one. + $request = $fiber->resume($this->processExprNode( + new Node\Stmt\Expression($request->expr), + $request->expr, + $request->scope->toMutatingScope(), + $storage->duplicate(), + new NoopNodeCallback(), + ExpressionContext::createTopLevel(), + )); $this->runFiberForNodeCallback($storage, $fiber, $request); // Break and restart the loop since the array may have been modified @@ -117,7 +133,7 @@ protected function processPendingFibers(ExpressionResultStorage $storage): void } } - private function processPendingFibersForRequestedExpr(ExpressionResultStorage $storage, Expr $expr, Scope $result): void + private function processPendingFibersForRequestedExpr(ExpressionResultStorage $storage, Expr $expr, ExpressionResult $expressionResult): void { start: @@ -130,7 +146,7 @@ private function processPendingFibersForRequestedExpr(ExpressionResultStorage $s unset($storage->pendingFibers[$key]); $fiber = $pending['fiber']; - $request = $fiber->resume($result); + $request = $fiber->resume($expressionResult); $this->runFiberForNodeCallback($storage, $fiber, $request); // Break and restart the loop since the array may have been modified diff --git a/src/Analyser/Fiber/FiberScope.php b/src/Analyser/Fiber/FiberScope.php index b02f322c358..698cf7fa1d0 100644 --- a/src/Analyser/Fiber/FiberScope.php +++ b/src/Analyser/Fiber/FiberScope.php @@ -4,6 +4,7 @@ use Fiber; use PhpParser\Node\Expr; +use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; @@ -11,6 +12,7 @@ use PHPStan\Reflection\ParameterReflection; use PHPStan\Type\Type; use function array_pop; +use function count; final class FiberScope extends MutatingScope { @@ -57,12 +59,20 @@ public function toMutatingScope(): MutatingScope /** @api */ public function getType(Expr $node): Type { - /** @var Scope $beforeScope */ - $beforeScope = Fiber::suspend( - new BeforeScopeForExprRequest($node, $this), + /** @var ExpressionResult $expressionResult */ + $expressionResult = Fiber::suspend( + new ExpressionResultRequest($node, $this), ); - $scope = $this->preprocessScope($beforeScope->toMutatingScope()); + if ( + !$this->nativeTypesPromoted + && count($this->truthyValueExprs) === 0 + && count($this->falseyValueExprs) === 0 + ) { + return $expressionResult->getType(); + } + + $scope = $this->preprocessScope($expressionResult->getBeforeScope()); return $scope->getType($node); } @@ -79,23 +89,31 @@ public function getScopeNativeType(Expr $expr): Type /** @api */ public function getNativeType(Expr $expr): Type { - /** @var Scope $beforeScope */ - $beforeScope = Fiber::suspend( - new BeforeScopeForExprRequest($expr, $this), + /** @var ExpressionResult $expressionResult */ + $expressionResult = Fiber::suspend( + new ExpressionResultRequest($expr, $this), ); - $scope = $this->preprocessScope($beforeScope->toMutatingScope()); + if ( + !$this->nativeTypesPromoted + && count($this->truthyValueExprs) === 0 + && count($this->falseyValueExprs) === 0 + ) { + return $expressionResult->getNativeType(); + } + + $scope = $this->preprocessScope($expressionResult->getBeforeScope()); return $scope->getNativeType($expr); } public function getKeepVoidType(Expr $node): Type { - /** @var Scope $beforeScope */ - $beforeScope = Fiber::suspend( - new BeforeScopeForExprRequest($node, $this), + /** @var ExpressionResult $expressionResult */ + $expressionResult = Fiber::suspend( + new ExpressionResultRequest($node, $this), ); - $scope = $this->preprocessScope($beforeScope->toMutatingScope()); + $scope = $this->preprocessScope($expressionResult->getBeforeScope()); return $scope->getKeepVoidType($node); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index f9181659c5c..3aea6d4876a 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -355,7 +355,7 @@ public function processNodes( $this->processPendingFibers($expressionResultStorage); } - public function storeBeforeScope(ExpressionResultStorage $storage, Expr $expr, Scope $beforeScope): void + public function storeExpressionResult(ExpressionResultStorage $storage, Expr $expr, ExpressionResult $expressionResult): void { } @@ -2735,7 +2735,6 @@ public function processExprNode( ExpressionContext $context, ): ExpressionResult { - $this->storeBeforeScope($storage, $expr, $scope); if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { if ($expr instanceof FuncCall) { $newExpr = new FunctionCallableNode($expr->name, $expr); @@ -2749,7 +2748,18 @@ public function processExprNode( throw new ShouldNotHappenException(); } - return $this->processExprNode($stmt, $newExpr, $scope, $storage, $nodeCallback, $context); + $newExprResult = $this->processExprNode($stmt, $newExpr, $scope, $storage, $nodeCallback, $context); + $expressionResult = $this->expressionResultFactory->create( + $newExprResult->getScope(), + beforeScope: $scope, + expr: $expr, + hasYield: $newExprResult->hasYield(), + isAlwaysTerminating: $newExprResult->isAlwaysTerminating(), + throwPoints: $newExprResult->getThrowPoints(), + impurePoints: $newExprResult->getImpurePoints(), + ); + $this->storeExpressionResult($storage, $expr, $expressionResult); + return $expressionResult; } $this->callNodeCallbackWithExpression($nodeCallback, $expr, $scope, $storage, $context); @@ -2760,15 +2770,12 @@ public function processExprNode( continue; } - return $exprHandler->processExpr($this, $stmt, $expr, $scope, $storage, $nodeCallback, $context); - } - - if ($expr instanceof List_) { - // only in assign and foreach, processed elsewhere - return $this->expressionResultFactory->create($scope, beforeScope: $scope, expr: $expr, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []); + $expressionResult = $exprHandler->processExpr($this, $stmt, $expr, $scope, $storage, $nodeCallback, $context); + $this->storeExpressionResult($storage, $expr, $expressionResult); + return $expressionResult; } - return $this->expressionResultFactory->create( + $expressionResult = $this->expressionResultFactory->create( $scope, beforeScope: $scope, expr: $expr, @@ -2777,6 +2784,9 @@ public function processExprNode( throwPoints: [], impurePoints: [], ); + $this->storeExpressionResult($storage, $expr, $expressionResult); + + return $expressionResult; } /** @@ -3642,7 +3652,15 @@ public function processArgs( $impurePoints = array_merge($impurePoints, $closureResult->getImpurePoints()); } - $this->storeBeforeScope($storage, $arg->value, $scopeToPass); + $this->storeExpressionResult($storage, $arg->value, new ExpressionResult( + $closureResult->getScope(), + $scopeToPass, + $arg->value, + hasYield: false, + isAlwaysTerminating: false, + throwPoints: [], + impurePoints: [], + )); $uses = []; foreach ($arg->value->uses as $use) { @@ -3700,7 +3718,7 @@ public function processArgs( $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()); } - $this->storeBeforeScope($storage, $arg->value, $scopeToPass); + $this->storeExpressionResult($storage, $arg->value, $arrowFunctionResult); } else { $exprType = $scope->getType($arg->value); $enterExpressionAssignForByRef = $assignByReference && $arg->value instanceof ArrayDimFetch && $arg->value->dim === null; From 0933ba357a29f0abfbf7c27eaaed3c8ae0dd5a12 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 11:23:57 +0200 Subject: [PATCH 05/22] Fill the missing gaps in expr processing --- src/Analyser/ExprHandler/AssignHandler.php | 6 ++++ src/Analyser/ExprHandler/PipeHandler.php | 19 ++++++++++++ src/Analyser/ExpressionResultStorage.php | 5 ++++ src/Analyser/Fiber/FiberNodeScopeResolver.php | 29 +++++++++++-------- src/Analyser/NodeScopeResolver.php | 29 +++++++++++++++++-- 5 files changed, 73 insertions(+), 15 deletions(-) diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 143c52da656..1cf5c4cc1c8 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -990,10 +990,16 @@ public function processAssignVar( $var = $var->getVar(); } + // the chain is usually a clone of AST nodes already processed elsewhere + // (see Unset_ handling) - process it with a noop callback so that + // results for its nodes are stored without invoking rules twice + $nodeScopeResolver->processExprNode($stmt, $var, $scope, $storage, new NoopNodeCallback(), $context->enterDeep()); + $offsetTypes = []; $offsetNativeTypes = []; foreach (array_reverse($dimFetchStack) as $dimFetch) { $dimExpr = $dimFetch->getDim(); + $nodeScopeResolver->processExprNode($stmt, $dimExpr, $scope, $storage, new NoopNodeCallback(), $context->enterDeep()); $offsetTypes[] = [$scope->getType($dimExpr), $dimFetch]; $offsetNativeTypes[] = [$scope->getNativeType($dimExpr), $dimFetch]; } diff --git a/src/Analyser/ExprHandler/PipeHandler.php b/src/Analyser/ExprHandler/PipeHandler.php index d3bbbc90413..a10009c2d56 100644 --- a/src/Analyser/ExprHandler/PipeHandler.php +++ b/src/Analyser/ExprHandler/PipeHandler.php @@ -69,24 +69,43 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex unset($rightAttributes[ExprPrinter::ATTRIBUTE_CACHE_KEY]); $argAttributes = $expr->getAttribute(ReversePipeTransformerVisitor::ARG_ATTRIBUTES_NAME, []); + $isRightFirstClassCallable = false; if ($expr->right instanceof FuncCall && $expr->right->isFirstClassCallable()) { $callExpr = new FuncCall($expr->right->name, [ new Arg($expr->left, attributes: $argAttributes), ], $rightAttributes); + $isRightFirstClassCallable = true; } elseif ($expr->right instanceof MethodCall && $expr->right->isFirstClassCallable()) { $callExpr = new MethodCall($expr->right->var, $expr->right->name, [ new Arg($expr->left, attributes: $argAttributes), ], $rightAttributes); + $isRightFirstClassCallable = true; } elseif ($expr->right instanceof StaticCall && $expr->right->isFirstClassCallable()) { $callExpr = new StaticCall($expr->right->class, $expr->right->name, [ new Arg($expr->left, attributes: $argAttributes), ], $rightAttributes); + $isRightFirstClassCallable = true; } else { $callExpr = new FuncCall($expr->right, [ new Arg($expr->left, attributes: $argAttributes), ], $rightAttributes); } + if ($isRightFirstClassCallable) { + // the original first-class callable node is not processed through + // processExprNode - store its result so that node callbacks asking + // about its type can be resumed + $nodeScopeResolver->storeExpressionResult($storage, $expr->right, $this->expressionResultFactory->create( + $scope, + beforeScope: $scope, + expr: $expr->right, + hasYield: false, + isAlwaysTerminating: false, + throwPoints: [], + impurePoints: [], + )); + } + $callResult = $nodeScopeResolver->processExprNode($stmt, $callExpr, $scope, $storage, $nodeCallback, $context); return $this->expressionResultFactory->create( diff --git a/src/Analyser/ExpressionResultStorage.php b/src/Analyser/ExpressionResultStorage.php index 9fe2d2d14bd..daea8cf3c93 100644 --- a/src/Analyser/ExpressionResultStorage.php +++ b/src/Analyser/ExpressionResultStorage.php @@ -33,6 +33,11 @@ public function duplicate(): self return $new; } + public function mergeResults(self $other): void + { + $this->exprResults->addAll($other->exprResults); + } + public function storeExpressionResult(Expr $expr, ExpressionResult $expressionResult): void { $this->exprResults[$expr] = $expressionResult; diff --git a/src/Analyser/Fiber/FiberNodeScopeResolver.php b/src/Analyser/Fiber/FiberNodeScopeResolver.php index a0340a3781b..2beceacb7d0 100644 --- a/src/Analyser/Fiber/FiberNodeScopeResolver.php +++ b/src/Analyser/Fiber/FiberNodeScopeResolver.php @@ -113,19 +113,24 @@ protected function processPendingFibers(ExpressionResultStorage $storage): void $fiber = $pending['fiber']; - // Process the expression with a duplicated storage so that the result + // Process the synthetic node with a duplicated storage so that the result // computed from the asker's scope does not poison the real storage. - // The expression might still be processed naturally later (e.g. a loop - // condition asked about by a rule before the loop converges) and other - // fibers need to wait for that result instead of this one. - $request = $fiber->resume($this->processExprNode( - new Node\Stmt\Expression($request->expr), - $request->expr, - $request->scope->toMutatingScope(), - $storage->duplicate(), - new NoopNodeCallback(), - ExpressionContext::createTopLevel(), - )); + // Real AST nodes contained in the synthetic node already have their + // results stored and are not processed again. + $this->returnStoredExpressionResults = true; + try { + $expressionResult = $this->processExprNode( + new Node\Stmt\Expression($request->expr), + $request->expr, + $request->scope->toMutatingScope(), + $storage->duplicate(), + new NoopNodeCallback(), + ExpressionContext::createTopLevel(), + ); + } finally { + $this->returnStoredExpressionResults = false; + } + $request = $fiber->resume($expressionResult); $this->runFiberForNodeCallback($storage, $fiber, $request); // Break and restart the loop since the array may have been modified diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 3aea6d4876a..1321188af92 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -207,6 +207,12 @@ class NodeScopeResolver /** @var array */ private array $calledMethodResults = []; + /** + * When processing a synthetic node on demand (for a Fiber request), real AST + * nodes contained in it were already processed and must not be processed again. + */ + protected bool $returnStoredExpressionResults = false; + /** * @param string[][] $earlyTerminatingMethodCalls className(string) => methods(string[]) * @param array $earlyTerminatingFunctionCalls @@ -1416,10 +1422,15 @@ public function processStmtNode( $throwPoints = []; $impurePoints = []; - $traitStorage = $storage->duplicate(); - $traitStorage->pendingFibers = []; + // fresh storage - the same trait node objects are processed once per + // using class and fibers must not see results from a previous pass + $traitStorage = new ExpressionResultStorage(); $this->processTraitUse($stmt, $scope, $traitStorage, $nodeCallback); $this->processPendingFibers($traitStorage); + + // class-level node callbacks (like ClassMethodsNode) are invoked with + // the outer storage but ask about expressions inside the used trait + $storage->mergeResults($traitStorage); } elseif ($stmt instanceof Foreach_) { if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) { $scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt); @@ -2735,6 +2746,13 @@ public function processExprNode( ExpressionContext $context, ): ExpressionResult { + if ($this->returnStoredExpressionResults) { + $storedResult = $storage->findExpressionResult($expr); + if ($storedResult !== null) { + return $storedResult; + } + } + if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { if ($expr instanceof FuncCall) { $newExpr = new FunctionCallableNode($expr->name, $expr); @@ -3652,7 +3670,7 @@ public function processArgs( $impurePoints = array_merge($impurePoints, $closureResult->getImpurePoints()); } - $this->storeExpressionResult($storage, $arg->value, new ExpressionResult( + $this->storeExpressionResult($storage, $arg->value, $this->expressionResultFactory->create( $closureResult->getScope(), $scopeToPass, $arg->value, @@ -4693,6 +4711,11 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection throw new ShouldNotHappenException(); } $traitScope = $scope->enterTrait($traitReflection); + + // attribute args are not processed as part of the trait statements + // but rules like TraitAttributesRule ask about their types + $this->processAttributeGroups($node, $node->attrGroups, $traitScope, $storage, new NoopNodeCallback()); + $this->callNodeCallback($nodeCallback, new InTraitNode($node, $traitReflection, $scope->getClassReflection()), $traitScope, $storage); $this->processStmtNodesInternal($node, $stmts, $traitScope, $storage, $nodeCallback, StatementContext::createTopLevel()); return; From cfef4b20a75fd341f335a1da18a14a7354ee7cfc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 11:52:11 +0200 Subject: [PATCH 06/22] Divide ExprHandler into TypeResolvingExprHandler Not all ExprHandlers will be TypeResolvingExprHandler coming into the future. Instead of resolveType+specifyTypes, they will pass callbacks into ExpressionResult doing similar job. --- src/Analyser/ExprHandler.php | 16 ---------- .../ExprHandler/ArrayDimFetchHandler.php | 6 ++-- src/Analyser/ExprHandler/ArrayHandler.php | 6 ++-- .../ExprHandler/ArrowFunctionHandler.php | 6 ++-- src/Analyser/ExprHandler/AssignHandler.php | 6 ++-- src/Analyser/ExprHandler/AssignOpHandler.php | 6 ++-- src/Analyser/ExprHandler/BinaryOpHandler.php | 6 ++-- .../ExprHandler/BitwiseNotHandler.php | 6 ++-- .../ExprHandler/BooleanAndHandler.php | 6 ++-- .../ExprHandler/BooleanNotHandler.php | 6 ++-- src/Analyser/ExprHandler/BooleanOrHandler.php | 6 ++-- src/Analyser/ExprHandler/CastHandler.php | 6 ++-- .../ExprHandler/CastStringHandler.php | 6 ++-- .../ExprHandler/ClassConstFetchHandler.php | 6 ++-- src/Analyser/ExprHandler/CloneHandler.php | 6 ++-- src/Analyser/ExprHandler/ClosureHandler.php | 6 ++-- src/Analyser/ExprHandler/CoalesceHandler.php | 6 ++-- .../ExprHandler/ConstFetchHandler.php | 6 ++-- src/Analyser/ExprHandler/EmptyHandler.php | 6 ++-- .../ExprHandler/ErrorSuppressHandler.php | 6 ++-- src/Analyser/ExprHandler/EvalHandler.php | 6 ++-- src/Analyser/ExprHandler/ExitHandler.php | 6 ++-- .../FirstClassCallableFuncCallHandler.php | 6 ++-- .../FirstClassCallableMethodCallHandler.php | 6 ++-- .../FirstClassCallableNewHandler.php | 6 ++-- .../FirstClassCallableStaticCallHandler.php | 6 ++-- src/Analyser/ExprHandler/FuncCallHandler.php | 6 ++-- src/Analyser/ExprHandler/IncludeHandler.php | 6 ++-- .../ExprHandler/InstanceofHandler.php | 6 ++-- .../ExprHandler/InterpolatedStringHandler.php | 6 ++-- src/Analyser/ExprHandler/IssetHandler.php | 6 ++-- src/Analyser/ExprHandler/MatchHandler.php | 6 ++-- .../ExprHandler/MethodCallHandler.php | 6 ++-- src/Analyser/ExprHandler/NewHandler.php | 6 ++-- .../ExprHandler/NullsafeMethodCallHandler.php | 6 ++-- .../NullsafePropertyFetchHandler.php | 6 ++-- src/Analyser/ExprHandler/PipeHandler.php | 6 ++-- src/Analyser/ExprHandler/PostDecHandler.php | 6 ++-- src/Analyser/ExprHandler/PostIncHandler.php | 6 ++-- src/Analyser/ExprHandler/PreDecHandler.php | 6 ++-- src/Analyser/ExprHandler/PreIncHandler.php | 6 ++-- src/Analyser/ExprHandler/PrintHandler.php | 6 ++-- .../ExprHandler/PropertyFetchHandler.php | 6 ++-- src/Analyser/ExprHandler/ScalarHandler.php | 6 ++-- .../ExprHandler/StaticCallHandler.php | 6 ++-- .../StaticPropertyFetchHandler.php | 6 ++-- src/Analyser/ExprHandler/TernaryHandler.php | 6 ++-- src/Analyser/ExprHandler/ThrowHandler.php | 6 ++-- .../ExprHandler/UnaryMinusHandler.php | 6 ++-- src/Analyser/ExprHandler/UnaryPlusHandler.php | 6 ++-- src/Analyser/ExprHandler/VariableHandler.php | 6 ++-- .../Virtual/AlwaysRememberedExprHandler.php | 6 ++-- .../Virtual/ExistingArrayDimFetchHandler.php | 6 ++-- .../Virtual/FunctionCallableNodeHandler.php | 6 ++-- .../Virtual/GetIterableKeyTypeExprHandler.php | 6 ++-- .../GetIterableValueTypeExprHandler.php | 6 ++-- .../Virtual/GetOffsetValueTypeExprHandler.php | 6 ++-- .../InstantiationCallableNodeHandler.php | 6 ++-- .../Virtual/MethodCallableNodeHandler.php | 6 ++-- .../Virtual/NativeTypeExprHandler.php | 6 ++-- .../OriginalPropertyTypeExprHandler.php | 6 ++-- .../SetExistingOffsetValueTypeExprHandler.php | 6 ++-- .../Virtual/SetOffsetValueTypeExprHandler.php | 6 ++-- .../StaticMethodCallableNodeHandler.php | 6 ++-- .../ExprHandler/Virtual/TypeExprHandler.php | 6 ++-- .../Virtual/UnsetOffsetExprHandler.php | 6 ++-- src/Analyser/ExprHandler/YieldFromHandler.php | 6 ++-- src/Analyser/ExprHandler/YieldHandler.php | 6 ++-- src/Analyser/MutatingScope.php | 3 ++ src/Analyser/TypeResolvingExprHandler.php | 30 +++++++++++++++++++ src/Analyser/TypeSpecifier.php | 3 ++ 71 files changed, 237 insertions(+), 217 deletions(-) create mode 100644 src/Analyser/TypeResolvingExprHandler.php diff --git a/src/Analyser/ExprHandler.php b/src/Analyser/ExprHandler.php index 98b8728ca40..7e93df1a6cf 100644 --- a/src/Analyser/ExprHandler.php +++ b/src/Analyser/ExprHandler.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Stmt; -use PHPStan\Type\Type; /** * @template T of Expr @@ -32,19 +31,4 @@ public function processExpr( ExpressionContext $context, ): ExpressionResult; - /** - * @param T $expr - */ - public function resolveType(MutatingScope $scope, Expr $expr): Type; - - /** - * @param T $expr - */ - public function specifyTypes( - TypeSpecifier $typeSpecifier, - Scope $scope, - Expr $expr, - TypeSpecifierContext $context, - ): SpecifiedTypes; - } diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index 0a0198cf3c2..14dec18a003 100644 --- a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php @@ -13,13 +13,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -30,10 +30,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ArrayDimFetchHandler implements ExprHandler +final class ArrayDimFetchHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index a8e58b932a4..6fe74abef78 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -12,11 +12,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -30,10 +30,10 @@ use function count; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ArrayHandler implements ExprHandler +final class ArrayHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/ArrowFunctionHandler.php b/src/Analyser/ExprHandler/ArrowFunctionHandler.php index 5390ee8e212..da01eb8e39f 100644 --- a/src/Analyser/ExprHandler/ArrowFunctionHandler.php +++ b/src/Analyser/ExprHandler/ArrowFunctionHandler.php @@ -9,22 +9,22 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ClosureTypeResolver; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ArrowFunctionHandler implements ExprHandler +final class ArrowFunctionHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 1cf5c4cc1c8..e0db4837ddc 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -28,7 +28,6 @@ use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExpressionTypeHolder; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; @@ -36,6 +35,7 @@ use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -85,10 +85,10 @@ use function is_string; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class AssignHandler implements ExprHandler +final class AssignHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index 5ebcff7b679..26d17c102be 100644 --- a/src/Analyser/ExprHandler/AssignOpHandler.php +++ b/src/Analyser/ExprHandler/AssignOpHandler.php @@ -13,13 +13,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -33,10 +33,10 @@ use function sprintf; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class AssignOpHandler implements ExprHandler +final class AssignOpHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/BinaryOpHandler.php b/src/Analyser/ExprHandler/BinaryOpHandler.php index c3d686ba134..516f54780a0 100644 --- a/src/Analyser/ExprHandler/BinaryOpHandler.php +++ b/src/Analyser/ExprHandler/BinaryOpHandler.php @@ -16,7 +16,6 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\EqualityTypeSpecifyingHelper; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; use PHPStan\Analyser\InternalThrowPoint; @@ -25,6 +24,7 @@ use PHPStan\Analyser\RicherScopeGetTypeHelper; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -54,10 +54,10 @@ use function strtolower; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class BinaryOpHandler implements ExprHandler +final class BinaryOpHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/BitwiseNotHandler.php b/src/Analyser/ExprHandler/BitwiseNotHandler.php index 4b6b6667823..5efa2fdef6d 100644 --- a/src/Analyser/ExprHandler/BitwiseNotHandler.php +++ b/src/Analyser/ExprHandler/BitwiseNotHandler.php @@ -9,11 +9,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -21,10 +21,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class BitwiseNotHandler implements ExprHandler +final class BitwiseNotHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/BooleanAndHandler.php b/src/Analyser/ExprHandler/BooleanAndHandler.php index 1265071cafa..779c7c014f1 100644 --- a/src/Analyser/ExprHandler/BooleanAndHandler.php +++ b/src/Analyser/ExprHandler/BooleanAndHandler.php @@ -12,13 +12,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ConditionalExpressionHolderHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -34,10 +34,10 @@ use function is_string; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class BooleanAndHandler implements ExprHandler +final class BooleanAndHandler implements TypeResolvingExprHandler { private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4; diff --git a/src/Analyser/ExprHandler/BooleanNotHandler.php b/src/Analyser/ExprHandler/BooleanNotHandler.php index 59cb5a986c1..fc4f263faee 100644 --- a/src/Analyser/ExprHandler/BooleanNotHandler.php +++ b/src/Analyser/ExprHandler/BooleanNotHandler.php @@ -9,11 +9,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -22,10 +22,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class BooleanNotHandler implements ExprHandler +final class BooleanNotHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/BooleanOrHandler.php b/src/Analyser/ExprHandler/BooleanOrHandler.php index 9b1d34d1cc2..c0ffac43f45 100644 --- a/src/Analyser/ExprHandler/BooleanOrHandler.php +++ b/src/Analyser/ExprHandler/BooleanOrHandler.php @@ -10,13 +10,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ConditionalExpressionHolderHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -33,10 +33,10 @@ use function count; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class BooleanOrHandler implements ExprHandler +final class BooleanOrHandler implements TypeResolvingExprHandler { private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4; diff --git a/src/Analyser/ExprHandler/CastHandler.php b/src/Analyser/ExprHandler/CastHandler.php index 6877fdd5b3e..af928d21ed1 100644 --- a/src/Analyser/ExprHandler/CastHandler.php +++ b/src/Analyser/ExprHandler/CastHandler.php @@ -15,11 +15,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -28,10 +28,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class CastHandler implements ExprHandler +final class CastHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/CastStringHandler.php b/src/Analyser/ExprHandler/CastStringHandler.php index bb13a0e2817..daa2c148be6 100644 --- a/src/Analyser/ExprHandler/CastStringHandler.php +++ b/src/Analyser/ExprHandler/CastStringHandler.php @@ -11,12 +11,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -25,10 +25,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class CastStringHandler implements ExprHandler +final class CastStringHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/ClassConstFetchHandler.php b/src/Analyser/ExprHandler/ClassConstFetchHandler.php index fe989f332c9..c00160c7c50 100644 --- a/src/Analyser/ExprHandler/ClassConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ClassConstFetchHandler.php @@ -10,11 +10,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -24,10 +24,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ClassConstFetchHandler implements ExprHandler +final class ClassConstFetchHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/CloneHandler.php b/src/Analyser/ExprHandler/CloneHandler.php index bc707347411..caddb7d0889 100644 --- a/src/Analyser/ExprHandler/CloneHandler.php +++ b/src/Analyser/ExprHandler/CloneHandler.php @@ -9,12 +9,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\Traverser\CloneTypeTraverser; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -24,10 +24,10 @@ use PHPStan\Type\TypeTraverser; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class CloneHandler implements ExprHandler +final class CloneHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/ClosureHandler.php b/src/Analyser/ExprHandler/ClosureHandler.php index b107c5843c6..65e801d6350 100644 --- a/src/Analyser/ExprHandler/ClosureHandler.php +++ b/src/Analyser/ExprHandler/ClosureHandler.php @@ -9,22 +9,22 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ClosureTypeResolver; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ClosureHandler implements ExprHandler +final class ClosureHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index f47554f2811..fa8cc9fcbc8 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -9,12 +9,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -27,10 +27,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class CoalesceHandler implements ExprHandler +final class CoalesceHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/ConstFetchHandler.php b/src/Analyser/ExprHandler/ConstFetchHandler.php index 17f429322e0..6f1518f163c 100644 --- a/src/Analyser/ExprHandler/ConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ConstFetchHandler.php @@ -11,11 +11,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -26,10 +26,10 @@ use function strtolower; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ConstFetchHandler implements ExprHandler +final class ConstFetchHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/EmptyHandler.php b/src/Analyser/ExprHandler/EmptyHandler.php index 54e9c397fa1..1939315de1b 100644 --- a/src/Analyser/ExprHandler/EmptyHandler.php +++ b/src/Analyser/ExprHandler/EmptyHandler.php @@ -10,12 +10,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -25,10 +25,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class EmptyHandler implements ExprHandler +final class EmptyHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/ErrorSuppressHandler.php b/src/Analyser/ExprHandler/ErrorSuppressHandler.php index ca006ebcedb..25bfa07bd36 100644 --- a/src/Analyser/ExprHandler/ErrorSuppressHandler.php +++ b/src/Analyser/ExprHandler/ErrorSuppressHandler.php @@ -9,21 +9,21 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ErrorSuppressHandler implements ExprHandler +final class ErrorSuppressHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/EvalHandler.php b/src/Analyser/ExprHandler/EvalHandler.php index 9bbbbe9fbd2..d4425781435 100644 --- a/src/Analyser/ExprHandler/EvalHandler.php +++ b/src/Analyser/ExprHandler/EvalHandler.php @@ -9,13 +9,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -24,10 +24,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class EvalHandler implements ExprHandler +final class EvalHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/ExitHandler.php b/src/Analyser/ExprHandler/ExitHandler.php index 7c1029c14e2..c459f38dc11 100644 --- a/src/Analyser/ExprHandler/ExitHandler.php +++ b/src/Analyser/ExprHandler/ExitHandler.php @@ -9,12 +9,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -23,10 +23,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ExitHandler implements ExprHandler +final class ExitHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/FirstClassCallableFuncCallHandler.php b/src/Analyser/ExprHandler/FirstClassCallableFuncCallHandler.php index 266996eaeb2..2fafbee4dbe 100644 --- a/src/Analyser/ExprHandler/FirstClassCallableFuncCallHandler.php +++ b/src/Analyser/ExprHandler/FirstClassCallableFuncCallHandler.php @@ -9,11 +9,11 @@ use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -24,10 +24,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class FirstClassCallableFuncCallHandler implements ExprHandler +final class FirstClassCallableFuncCallHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/FirstClassCallableMethodCallHandler.php b/src/Analyser/ExprHandler/FirstClassCallableMethodCallHandler.php index 1cafdd5b120..e487de41fb6 100644 --- a/src/Analyser/ExprHandler/FirstClassCallableMethodCallHandler.php +++ b/src/Analyser/ExprHandler/FirstClassCallableMethodCallHandler.php @@ -10,11 +10,11 @@ use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -24,10 +24,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class FirstClassCallableMethodCallHandler implements ExprHandler +final class FirstClassCallableMethodCallHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/FirstClassCallableNewHandler.php b/src/Analyser/ExprHandler/FirstClassCallableNewHandler.php index e158a8cc7b8..9f5e05c198d 100644 --- a/src/Analyser/ExprHandler/FirstClassCallableNewHandler.php +++ b/src/Analyser/ExprHandler/FirstClassCallableNewHandler.php @@ -9,11 +9,11 @@ use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -23,10 +23,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class FirstClassCallableNewHandler implements ExprHandler +final class FirstClassCallableNewHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/FirstClassCallableStaticCallHandler.php b/src/Analyser/ExprHandler/FirstClassCallableStaticCallHandler.php index 4d3519cf944..4a5c3cd0645 100644 --- a/src/Analyser/ExprHandler/FirstClassCallableStaticCallHandler.php +++ b/src/Analyser/ExprHandler/FirstClassCallableStaticCallHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -22,10 +22,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class FirstClassCallableStaticCallHandler implements ExprHandler +final class FirstClassCallableStaticCallHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index a36f1ba1c94..40d595a7735 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -18,7 +18,6 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\VoidToNullTypeTransformer; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\InternalThrowPoint; @@ -27,6 +26,7 @@ use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredParameter; @@ -82,10 +82,10 @@ use function str_starts_with; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class FuncCallHandler implements ExprHandler +final class FuncCallHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/IncludeHandler.php b/src/Analyser/ExprHandler/IncludeHandler.php index 2a0dd18b37f..528d2f0bd1d 100644 --- a/src/Analyser/ExprHandler/IncludeHandler.php +++ b/src/Analyser/ExprHandler/IncludeHandler.php @@ -9,13 +9,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -25,10 +25,10 @@ use function in_array; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class IncludeHandler implements ExprHandler +final class IncludeHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/InstanceofHandler.php b/src/Analyser/ExprHandler/InstanceofHandler.php index b5288696912..885a418f846 100644 --- a/src/Analyser/ExprHandler/InstanceofHandler.php +++ b/src/Analyser/ExprHandler/InstanceofHandler.php @@ -10,11 +10,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -33,10 +33,10 @@ use function strtolower; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class InstanceofHandler implements ExprHandler +final class InstanceofHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/InterpolatedStringHandler.php b/src/Analyser/ExprHandler/InterpolatedStringHandler.php index cdf2bdba301..75547f573bb 100644 --- a/src/Analyser/ExprHandler/InterpolatedStringHandler.php +++ b/src/Analyser/ExprHandler/InterpolatedStringHandler.php @@ -10,12 +10,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -25,10 +25,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class InterpolatedStringHandler implements ExprHandler +final class InterpolatedStringHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/IssetHandler.php b/src/Analyser/ExprHandler/IssetHandler.php index 9fc6c135a1c..7d379b16556 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.php @@ -17,13 +17,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -52,10 +52,10 @@ use function is_string; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class IssetHandler implements ExprHandler +final class IssetHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/MatchHandler.php b/src/Analyser/ExprHandler/MatchHandler.php index 60ac5f5b88b..981d9b78619 100644 --- a/src/Analyser/ExprHandler/MatchHandler.php +++ b/src/Analyser/ExprHandler/MatchHandler.php @@ -18,12 +18,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredParameter; @@ -49,10 +49,10 @@ use const SORT_NUMERIC; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class MatchHandler implements ExprHandler +final class MatchHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index cd521235237..854c660bb34 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -13,7 +13,6 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\MethodCallReturnTypeHelper; use PHPStan\Analyser\ExprHandler\Helper\MethodThrowPointHelper; use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper; @@ -23,6 +22,7 @@ use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredParameter; @@ -49,10 +49,10 @@ use function strtolower; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class MethodCallHandler implements ExprHandler +final class MethodCallHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index a70924f2118..d96d297d4f6 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -13,7 +13,6 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; @@ -25,6 +24,7 @@ use PHPStan\Analyser\ThrowPoint; use PHPStan\Analyser\Traverser\ConstructorClassTemplateTraverser; use PHPStan\Analyser\Traverser\GenericTypeTemplateTraverser; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredParameter; @@ -65,10 +65,10 @@ use function sprintf; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class NewHandler implements ExprHandler +final class NewHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php index 215c1d6b497..b5db844ed26 100644 --- a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php +++ b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php @@ -14,12 +14,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -30,10 +30,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class NullsafeMethodCallHandler implements ExprHandler +final class NullsafeMethodCallHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php index b5d253da2b0..017a57b723d 100644 --- a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php @@ -14,12 +14,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -30,10 +30,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class NullsafePropertyFetchHandler implements ExprHandler +final class NullsafePropertyFetchHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/PipeHandler.php b/src/Analyser/ExprHandler/PipeHandler.php index a10009c2d56..45a5e23322d 100644 --- a/src/Analyser/ExprHandler/PipeHandler.php +++ b/src/Analyser/ExprHandler/PipeHandler.php @@ -13,11 +13,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -27,10 +27,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class PipeHandler implements ExprHandler +final class PipeHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/PostDecHandler.php b/src/Analyser/ExprHandler/PostDecHandler.php index ecdf3bd84d8..0cff682498c 100644 --- a/src/Analyser/ExprHandler/PostDecHandler.php +++ b/src/Analyser/ExprHandler/PostDecHandler.php @@ -10,21 +10,21 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class PostDecHandler implements ExprHandler +final class PostDecHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/PostIncHandler.php b/src/Analyser/ExprHandler/PostIncHandler.php index 9a68af90336..a04b0cfea75 100644 --- a/src/Analyser/ExprHandler/PostIncHandler.php +++ b/src/Analyser/ExprHandler/PostIncHandler.php @@ -10,21 +10,21 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class PostIncHandler implements ExprHandler +final class PostIncHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/PreDecHandler.php b/src/Analyser/ExprHandler/PreDecHandler.php index 6569fde8c10..221376fd209 100644 --- a/src/Analyser/ExprHandler/PreDecHandler.php +++ b/src/Analyser/ExprHandler/PreDecHandler.php @@ -11,11 +11,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -35,10 +35,10 @@ use function str_decrement; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class PreDecHandler implements ExprHandler +final class PreDecHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/PreIncHandler.php b/src/Analyser/ExprHandler/PreIncHandler.php index 7d4be597076..73fd74c7dc5 100644 --- a/src/Analyser/ExprHandler/PreIncHandler.php +++ b/src/Analyser/ExprHandler/PreIncHandler.php @@ -11,11 +11,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -36,10 +36,10 @@ use function str_increment; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class PreIncHandler implements ExprHandler +final class PreIncHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/PrintHandler.php b/src/Analyser/ExprHandler/PrintHandler.php index cd6a90aee17..2b21db38ec4 100644 --- a/src/Analyser/ExprHandler/PrintHandler.php +++ b/src/Analyser/ExprHandler/PrintHandler.php @@ -9,13 +9,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -24,10 +24,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class PrintHandler implements ExprHandler +final class PrintHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/PropertyFetchHandler.php b/src/Analyser/ExprHandler/PropertyFetchHandler.php index 61152971d82..f8a4ab70259 100644 --- a/src/Analyser/ExprHandler/PropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/PropertyFetchHandler.php @@ -11,13 +11,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -32,10 +32,10 @@ use function count; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class PropertyFetchHandler implements ExprHandler +final class PropertyFetchHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/ScalarHandler.php b/src/Analyser/ExprHandler/ScalarHandler.php index 9b4de986801..dba4a0669da 100644 --- a/src/Analyser/ExprHandler/ScalarHandler.php +++ b/src/Analyser/ExprHandler/ScalarHandler.php @@ -10,11 +10,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -23,10 +23,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ScalarHandler implements ExprHandler +final class ScalarHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index 35b391d6e0f..abd772f9d40 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -16,7 +16,6 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\MethodCallReturnTypeHelper; use PHPStan\Analyser\ExprHandler\Helper\MethodThrowPointHelper; use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper; @@ -27,6 +26,7 @@ use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredParameter; @@ -57,10 +57,10 @@ use function strtolower; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class StaticCallHandler implements ExprHandler +final class StaticCallHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php index c6d61ddd866..4a1388c50e4 100644 --- a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php @@ -13,13 +13,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -33,10 +33,10 @@ use function count; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class StaticPropertyFetchHandler implements ExprHandler +final class StaticPropertyFetchHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/TernaryHandler.php b/src/Analyser/ExprHandler/TernaryHandler.php index 101ffe45330..bccabd76e0b 100644 --- a/src/Analyser/ExprHandler/TernaryHandler.php +++ b/src/Analyser/ExprHandler/TernaryHandler.php @@ -11,12 +11,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -26,10 +26,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class TernaryHandler implements ExprHandler +final class TernaryHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/ThrowHandler.php b/src/Analyser/ExprHandler/ThrowHandler.php index 05bda29ec2d..8231e9b6726 100644 --- a/src/Analyser/ExprHandler/ThrowHandler.php +++ b/src/Analyser/ExprHandler/ThrowHandler.php @@ -9,12 +9,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -23,10 +23,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ThrowHandler implements ExprHandler +final class ThrowHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/UnaryMinusHandler.php b/src/Analyser/ExprHandler/UnaryMinusHandler.php index ea67e7dabc4..ad84baeee02 100644 --- a/src/Analyser/ExprHandler/UnaryMinusHandler.php +++ b/src/Analyser/ExprHandler/UnaryMinusHandler.php @@ -9,11 +9,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -21,10 +21,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class UnaryMinusHandler implements ExprHandler +final class UnaryMinusHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/UnaryPlusHandler.php b/src/Analyser/ExprHandler/UnaryPlusHandler.php index 6ec1abe38fc..c3f38936c05 100644 --- a/src/Analyser/ExprHandler/UnaryPlusHandler.php +++ b/src/Analyser/ExprHandler/UnaryPlusHandler.php @@ -9,11 +9,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -21,10 +21,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class UnaryPlusHandler implements ExprHandler +final class UnaryPlusHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/VariableHandler.php b/src/Analyser/ExprHandler/VariableHandler.php index 6082dea55f2..70ce8439856 100644 --- a/src/Analyser/ExprHandler/VariableHandler.php +++ b/src/Analyser/ExprHandler/VariableHandler.php @@ -11,12 +11,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -29,10 +29,10 @@ use function is_string; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class VariableHandler implements ExprHandler +final class VariableHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php index 99d6d9925e8..d4700f6e048 100644 --- a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -20,10 +20,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class AlwaysRememberedExprHandler implements ExprHandler +final class AlwaysRememberedExprHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php index 411d2ee8d65..61b7884270d 100644 --- a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -20,10 +20,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class ExistingArrayDimFetchHandler implements ExprHandler +final class ExistingArrayDimFetchHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php index e56509e627e..b27515f40fd 100644 --- a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -21,10 +21,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class FunctionCallableNodeHandler implements ExprHandler +final class FunctionCallableNodeHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php index 127ef939539..6f32dd6c6ec 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -20,10 +20,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class GetIterableKeyTypeExprHandler implements ExprHandler +final class GetIterableKeyTypeExprHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php index 56b7b9be00e..950d1025266 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -20,10 +20,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class GetIterableValueTypeExprHandler implements ExprHandler +final class GetIterableValueTypeExprHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php index ed0b5da45c5..ad70185384f 100644 --- a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -20,10 +20,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class GetOffsetValueTypeExprHandler implements ExprHandler +final class GetOffsetValueTypeExprHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php index 937b6618d85..304aedb86ad 100644 --- a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -21,10 +21,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class InstantiationCallableNodeHandler implements ExprHandler +final class InstantiationCallableNodeHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php index 28492541bce..25ca81bbcd0 100644 --- a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -22,10 +22,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class MethodCallableNodeHandler implements ExprHandler +final class MethodCallableNodeHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php index 852d00b14cd..08715ad31cc 100644 --- a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -20,10 +20,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class NativeTypeExprHandler implements ExprHandler +final class NativeTypeExprHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php index 2621d242cc7..0e346c921c5 100644 --- a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -22,10 +22,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class OriginalPropertyTypeExprHandler implements ExprHandler +final class OriginalPropertyTypeExprHandler implements TypeResolvingExprHandler { public function __construct( diff --git a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php index 11487e514f2..56a154af6f8 100644 --- a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -22,10 +22,10 @@ use PHPStan\Type\UnionType; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class SetExistingOffsetValueTypeExprHandler implements ExprHandler +final class SetExistingOffsetValueTypeExprHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php index c85440094b8..08659b35101 100644 --- a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -22,10 +22,10 @@ use PHPStan\Type\UnionType; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class SetOffsetValueTypeExprHandler implements ExprHandler +final class SetOffsetValueTypeExprHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php index b12d7e120e5..8f9570ee5ec 100644 --- a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -22,10 +22,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class StaticMethodCallableNodeHandler implements ExprHandler +final class StaticMethodCallableNodeHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php index 6ca636fe081..32208c6a569 100644 --- a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -20,10 +20,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class TypeExprHandler implements ExprHandler +final class TypeExprHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php index d414e648fd2..9f5e4368b27 100644 --- a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php @@ -8,11 +8,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -20,10 +20,10 @@ use PHPStan\Type\Type; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class UnsetOffsetExprHandler implements ExprHandler +final class UnsetOffsetExprHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/YieldFromHandler.php b/src/Analyser/ExprHandler/YieldFromHandler.php index 1aac8244af7..ca086700866 100644 --- a/src/Analyser/ExprHandler/YieldFromHandler.php +++ b/src/Analyser/ExprHandler/YieldFromHandler.php @@ -10,13 +10,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -26,10 +26,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class YieldFromHandler implements ExprHandler +final class YieldFromHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/ExprHandler/YieldHandler.php b/src/Analyser/ExprHandler/YieldHandler.php index 07abbc7e6ee..dd8a5478fc0 100644 --- a/src/Analyser/ExprHandler/YieldHandler.php +++ b/src/Analyser/ExprHandler/YieldHandler.php @@ -10,13 +10,13 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; -use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -26,10 +26,10 @@ use function array_merge; /** - * @implements ExprHandler + * @implements TypeResolvingExprHandler */ #[AutowiredService] -final class YieldHandler implements ExprHandler +final class YieldHandler implements TypeResolvingExprHandler { public function __construct(private ExpressionResultFactory $expressionResultFactory) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c81d35e98fd..441cd08a6d9 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -986,6 +986,9 @@ private function resolveType(string $exprString, Expr $node): Type /** @var ExprHandler $exprHandler */ foreach ($this->container->getServicesByTag(ExprHandler::EXTENSION_TAG) as $exprHandler) { + if (!$exprHandler instanceof TypeResolvingExprHandler) { + continue; + } if (!$exprHandler->supports($node)) { continue; } diff --git a/src/Analyser/TypeResolvingExprHandler.php b/src/Analyser/TypeResolvingExprHandler.php new file mode 100644 index 00000000000..030a2dfb8a3 --- /dev/null +++ b/src/Analyser/TypeResolvingExprHandler.php @@ -0,0 +1,30 @@ + + */ +interface TypeResolvingExprHandler extends ExprHandler +{ + + /** + * @param T $expr + */ + public function resolveType(MutatingScope $scope, Expr $expr): Type; + + /** + * @param T $expr + */ + public function specifyTypes( + TypeSpecifier $typeSpecifier, + Scope $scope, + Expr $expr, + TypeSpecifierContext $context, + ): SpecifiedTypes; + +} diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 27156a8b3f0..dd1982e6694 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -91,6 +91,9 @@ public function specifyTypesInCondition( /** @var ExprHandler $exprHandler */ foreach ($this->container->getServicesByTag(ExprHandler::EXTENSION_TAG) as $exprHandler) { + if (!$exprHandler instanceof TypeResolvingExprHandler) { + continue; + } if (!$exprHandler->supports($expr)) { continue; } From 445afe8f6d708a8606ada00ec2e0ceed48a09945 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 12:21:16 +0200 Subject: [PATCH 07/22] ScalarHandler stops implementing TypeResolvingExprHandler --- src/Analyser/ExprHandler/ScalarHandler.php | 22 +++--------- src/Analyser/ExpressionResult.php | 40 ++++++++++++++++++++-- src/Analyser/ExpressionResultFactory.php | 3 ++ src/Analyser/MutatingScope.php | 2 +- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/Analyser/ExprHandler/ScalarHandler.php b/src/Analyser/ExprHandler/ScalarHandler.php index dba4a0669da..690ec8b8dd6 100644 --- a/src/Analyser/ExprHandler/ScalarHandler.php +++ b/src/Analyser/ExprHandler/ScalarHandler.php @@ -10,23 +10,19 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; -use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeResolvingExprHandler; -use PHPStan\Analyser\TypeSpecifier; -use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\Type\Type; /** - * @implements TypeResolvingExprHandler + * @implements ExprHandler */ #[AutowiredService] -final class ScalarHandler implements TypeResolvingExprHandler +final class ScalarHandler implements ExprHandler { public function __construct( @@ -43,6 +39,7 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + // TODO $typeSpecifier->specifyDefaultTypes($scope, $expr, $context) OR noop return $this->expressionResultFactory->create( $scope, beforeScope: $scope, @@ -51,17 +48,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex isAlwaysTerminating: false, throwPoints: [], impurePoints: [], + typeCallback: fn (Scope $scope) => $this->initializerExprTypeResolver->getType($expr, InitializerExprContext::fromScope($scope)), ); } - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - return $this->initializerExprTypeResolver->getType($expr, InitializerExprContext::fromScope($scope)); - } - - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); - } - } diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 7669a67a2c8..4bc44318c57 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -4,12 +4,17 @@ use PhpParser\Node\Expr; use PHPStan\DependencyInjection\GenerateFactory; +use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider; use PHPStan\Type\Type; +use PHPStan\Type\TypeUtils; #[GenerateFactory(interface: ExpressionResultFactory::class)] final class ExpressionResult { + /** @var (callable(MutatingScope, Expr): Type)|null */ + private $typeCallback; + /** @var (callable(): MutatingScope)|null */ private $truthyScopeCallback; @@ -20,13 +25,19 @@ final class ExpressionResult private ?MutatingScope $falseyScope = null; + private ?Type $cachedType = null; + + private ?Type $cachedNativeType = null; + /** * @param InternalThrowPoint[] $throwPoints * @param ImpurePoint[] $impurePoints + * @param (callable(MutatingScope, Expr): Type)|null $typeCallback * @param (callable(): MutatingScope)|null $truthyScopeCallback * @param (callable(): MutatingScope)|null $falseyScopeCallback */ public function __construct( + private ExpressionTypeResolverExtensionRegistryProvider $expressionTypeResolverExtensionRegistryProvider, private MutatingScope $scope, private MutatingScope $beforeScope, private Expr $expr, @@ -36,10 +47,12 @@ public function __construct( private array $impurePoints, ?callable $truthyScopeCallback = null, ?callable $falseyScopeCallback = null, + ?callable $typeCallback = null, ) { $this->truthyScopeCallback = $truthyScopeCallback; $this->falseyScopeCallback = $falseyScopeCallback; + $this->typeCallback = $typeCallback; } public function getScope(): MutatingScope @@ -108,12 +121,35 @@ public function isAlwaysTerminating(): bool public function getType(): Type { - return $this->beforeScope->getType($this->expr); + if ($this->cachedType !== null) { + return $this->cachedType; + } + + foreach ($this->expressionTypeResolverExtensionRegistryProvider->getRegistry()->getExtensions() as $extension) { + $type = $extension->getType($this->expr, $this->beforeScope); + if ($type !== null) { + return $this->cachedType = $type; + } + } + + if ($this->typeCallback !== null) { + return $this->cachedType = TypeUtils::resolveLateResolvableTypes(($this->typeCallback)($this->beforeScope, $this->expr)); + } + + return $this->cachedType = $this->beforeScope->getType($this->expr); } public function getNativeType(): Type { - return $this->beforeScope->getNativeType($this->expr); + if ($this->cachedNativeType !== null) { + return $this->cachedNativeType; + } + + if ($this->typeCallback !== null) { + return $this->cachedNativeType = TypeUtils::resolveLateResolvableTypes(($this->typeCallback)($this->beforeScope->doNotTreatPhpDocTypesAsCertain(), $this->expr)); + } + + return $this->cachedNativeType = $this->beforeScope->getNativeType($this->expr); } } diff --git a/src/Analyser/ExpressionResultFactory.php b/src/Analyser/ExpressionResultFactory.php index 255d6258fba..d7c5955f4c1 100644 --- a/src/Analyser/ExpressionResultFactory.php +++ b/src/Analyser/ExpressionResultFactory.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser; use PhpParser\Node\Expr; +use PHPStan\Type\Type; interface ExpressionResultFactory { @@ -12,6 +13,7 @@ interface ExpressionResultFactory * @param ImpurePoint[] $impurePoints * @param (callable(): MutatingScope)|null $truthyScopeCallback * @param (callable(): MutatingScope)|null $falseyScopeCallback + * @param (callable(MutatingScope, Expr): Type)|null $typeCallback */ public function create( MutatingScope $scope, @@ -23,6 +25,7 @@ public function create( array $impurePoints, ?callable $truthyScopeCallback = null, ?callable $falseyScopeCallback = null, + ?callable $typeCallback = null, ): ExpressionResult; } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 441cd08a6d9..26ee10770e8 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1198,7 +1198,7 @@ public function getKeepVoidType(Expr $node): Type return $this->getType($clonedNode); } - public function doNotTreatPhpDocTypesAsCertain(): Scope + public function doNotTreatPhpDocTypesAsCertain(): self { return $this->promoteNativeTypes(); } From f729e60b0e47fa7b526a55eb668e11ee85916edc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 18:46:29 +0200 Subject: [PATCH 08/22] ExpressionResultStorageStack - answer type questions from ExpressionResults Old-world consumers (TypeSpecifier dispatcher, extensions, rules below PHP 8.1, unconverted handlers' resolveType) keep working for nodes whose handler no longer implements TypeResolvingExprHandler: every scope shares the ExpressionResultStorageStack created by its internal scope factory, NodeScopeResolver pushes the storage of the analysis in progress through MutatingScope::pushExpressionResultStorage() (always popped in finally), and MutatingScope resolves such nodes from the stored result - or by processing a synthetic node on demand. Also adds MutatingScope::applySpecifiedTypes (filterBySpecifiedTypes without Scope::getType()) and the specifyTypesCallback slot on ExpressionResult consulted by getTruthyScope()/getFalseyScope(). The cycle collector is disabled in bin/phpstan - scopes deliberately never reference a storage directly, only the stack. Popping severs the stack->storage edge when an analysis ends, so retained scopes do not pin the whole result graph. Co-Authored-By: Claude Fable 5 --- phpstan-baseline.neon | 2 +- src/Analyser/DirectInternalScopeFactory.php | 30 +-- src/Analyser/ExpressionResult.php | 36 +++ src/Analyser/ExpressionResultFactory.php | 2 + src/Analyser/ExpressionResultStorage.php | 10 +- src/Analyser/ExpressionResultStorageStack.php | 52 ++++ src/Analyser/Fiber/FiberNodeScopeResolver.php | 27 +- src/Analyser/LazyInternalScopeFactory.php | 9 +- src/Analyser/MutatingScope.php | 241 ++++++++++++++++-- src/Analyser/NodeScopeResolver.php | 74 +++++- 10 files changed, 412 insertions(+), 71 deletions(-) create mode 100644 src/Analyser/ExpressionResultStorageStack.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2c3e0d16c3c..9b17fcbb52e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -69,7 +69,7 @@ parameters: - rawMessage: Casting to string something that's already string. identifier: cast.useless - count: 3 + count: 5 path: src/Analyser/MutatingScope.php - diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index 533d37c5f92..02afcab90e5 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -19,6 +19,8 @@ final class DirectInternalScopeFactory implements InternalScopeFactory { + private ExpressionResultStorageStack $expressionResultStorageStack; + /** * @param int|array{min: int, max: int}|null $configPhpVersion * @param callable(Node $node, Scope $scope): void|null $nodeCallback @@ -38,8 +40,10 @@ public function __construct( private $nodeCallback, private ConstantResolver $constantResolver, private bool $fiber = false, + ?ExpressionResultStorageStack $expressionResultStorageStack = null, ) { + $this->expressionResultStorageStack = $expressionResultStorageStack ?? new ExpressionResultStorageStack(); } public function create( @@ -77,6 +81,7 @@ public function create( $this->propertyReflectionFinder, $this->parser, $this->constantResolver, + $this->expressionResultStorageStack, $context, $this->phpVersion, $this->attributeReflectionFactory, @@ -102,25 +107,15 @@ public function create( public function toFiberFactory(): InternalScopeFactory { - return new self( - $this->container, - $this->reflectionProvider, - $this->initializerExprTypeResolver, - $this->expressionTypeResolverExtensionRegistryProvider, - $this->exprPrinter, - $this->typeSpecifier, - $this->propertyReflectionFinder, - $this->parser, - $this->phpVersion, - $this->attributeReflectionFactory, - $this->configPhpVersion, - $this->nodeCallback, - $this->constantResolver, - true, - ); + return $this->withFlavor(true); } public function toMutatingFactory(): InternalScopeFactory + { + return $this->withFlavor(false); + } + + private function withFlavor(bool $fiber): self { return new self( $this->container, @@ -136,7 +131,8 @@ public function toMutatingFactory(): InternalScopeFactory $this->configPhpVersion, $this->nodeCallback, $this->constantResolver, - false, + $fiber, + $this->expressionResultStorageStack, ); } diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 4bc44318c57..9128ea6d2e0 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -15,6 +15,9 @@ final class ExpressionResult /** @var (callable(MutatingScope, Expr): Type)|null */ private $typeCallback; + /** @var (callable(MutatingScope, TypeSpecifierContext): SpecifiedTypes)|null */ + private $specifyTypesCallback; + /** @var (callable(): MutatingScope)|null */ private $truthyScopeCallback; @@ -33,6 +36,7 @@ final class ExpressionResult * @param InternalThrowPoint[] $throwPoints * @param ImpurePoint[] $impurePoints * @param (callable(MutatingScope, Expr): Type)|null $typeCallback + * @param (callable(MutatingScope, TypeSpecifierContext): SpecifiedTypes)|null $specifyTypesCallback * @param (callable(): MutatingScope)|null $truthyScopeCallback * @param (callable(): MutatingScope)|null $falseyScopeCallback */ @@ -48,11 +52,13 @@ public function __construct( ?callable $truthyScopeCallback = null, ?callable $falseyScopeCallback = null, ?callable $typeCallback = null, + ?callable $specifyTypesCallback = null, ) { $this->truthyScopeCallback = $truthyScopeCallback; $this->falseyScopeCallback = $falseyScopeCallback; $this->typeCallback = $typeCallback; + $this->specifyTypesCallback = $specifyTypesCallback; } public function getScope(): MutatingScope @@ -93,6 +99,12 @@ public function getTruthyScope(): MutatingScope } if ($this->truthyScopeCallback === null) { + if ($this->specifyTypesCallback !== null) { + return $this->truthyScope = $this->scope->applySpecifiedTypes( + ($this->specifyTypesCallback)($this->scope, TypeSpecifierContext::createTruthy()), + ); + } + return $this->truthyScope = $this->scope->filterByTruthyValue($this->expr); } @@ -107,6 +119,12 @@ public function getFalseyScope(): MutatingScope } if ($this->falseyScopeCallback === null) { + if ($this->specifyTypesCallback !== null) { + return $this->falseyScope = $this->scope->applySpecifiedTypes( + ($this->specifyTypesCallback)($this->scope, TypeSpecifierContext::createFalsey()), + ); + } + return $this->falseyScope = $this->scope->filterByFalseyValue($this->expr); } @@ -152,4 +170,22 @@ public function getNativeType(): Type return $this->cachedNativeType = $this->beforeScope->getNativeType($this->expr); } + public function hasTypeCallback(): bool + { + return $this->typeCallback !== null; + } + + /** + * Re-evaluates the expression type on a different scope (e.g. a narrowed one). + * Unlike getType(), the result is not cached. + */ + public function getTypeForScope(MutatingScope $scope): Type + { + if ($this->typeCallback !== null) { + return TypeUtils::resolveLateResolvableTypes(($this->typeCallback)($scope, $this->expr)); + } + + return $scope->getType($this->expr); + } + } diff --git a/src/Analyser/ExpressionResultFactory.php b/src/Analyser/ExpressionResultFactory.php index d7c5955f4c1..43926896f5f 100644 --- a/src/Analyser/ExpressionResultFactory.php +++ b/src/Analyser/ExpressionResultFactory.php @@ -14,6 +14,7 @@ interface ExpressionResultFactory * @param (callable(): MutatingScope)|null $truthyScopeCallback * @param (callable(): MutatingScope)|null $falseyScopeCallback * @param (callable(MutatingScope, Expr): Type)|null $typeCallback + * @param (callable(MutatingScope, TypeSpecifierContext): SpecifiedTypes)|null $specifyTypesCallback */ public function create( MutatingScope $scope, @@ -26,6 +27,7 @@ public function create( ?callable $truthyScopeCallback = null, ?callable $falseyScopeCallback = null, ?callable $typeCallback = null, + ?callable $specifyTypesCallback = null, ): ExpressionResult; } diff --git a/src/Analyser/ExpressionResultStorage.php b/src/Analyser/ExpressionResultStorage.php index daea8cf3c93..b04bfb507e2 100644 --- a/src/Analyser/ExpressionResultStorage.php +++ b/src/Analyser/ExpressionResultStorage.php @@ -15,6 +15,12 @@ final class ExpressionResultStorage /** @var SplObjectStorage */ private SplObjectStorage $exprResults; + /** + * Read-only fallback - writes never reach it. Makes duplicate() O(1) + * instead of copying all stored results. + */ + private ?self $fallback = null; + /** @var array, request: ExpressionResultRequest}> */ public array $pendingFibers = []; @@ -29,7 +35,7 @@ public function __construct() public function duplicate(): self { $new = new self(); - $new->exprResults->addAll($this->exprResults); + $new->fallback = $this; return $new; } @@ -45,7 +51,7 @@ public function storeExpressionResult(Expr $expr, ExpressionResult $expressionRe public function findExpressionResult(Expr $expr): ?ExpressionResult { - return $this->exprResults[$expr] ?? null; + return $this->exprResults[$expr] ?? $this->fallback?->findExpressionResult($expr); } } diff --git a/src/Analyser/ExpressionResultStorageStack.php b/src/Analyser/ExpressionResultStorageStack.php new file mode 100644 index 00000000000..3bf644f7965 --- /dev/null +++ b/src/Analyser/ExpressionResultStorageStack.php @@ -0,0 +1,52 @@ + results -> scopes -> storage) + * that never gets collected because the cycle collector is disabled + * in bin/phpstan. + * + * NodeScopeResolver pushes a storage for the duration of an analysis (file, + * statement list, trait pass, on-demand expression) through + * MutatingScope::pushExpressionResultStorage() and must always pop it + * in a finally block. Old-world type questions about expressions whose + * handler no longer implements TypeResolvingExprHandler are answered from + * the current storage (see MutatingScope::resolveTypeOfNewWorldHandlerNode()). + * A scope used outside any running analysis simply misses here and resolves + * on demand with a throwaway storage. + */ +final class ExpressionResultStorageStack +{ + + /** @var list */ + private array $stack = []; + + public function push(ExpressionResultStorage $storage): void + { + $this->stack[] = $storage; + } + + public function pop(): void + { + array_pop($this->stack); + } + + public function getCurrent(): ?ExpressionResultStorage + { + if (count($this->stack) === 0) { + return null; + } + + return $this->stack[count($this->stack) - 1]; + } + +} diff --git a/src/Analyser/Fiber/FiberNodeScopeResolver.php b/src/Analyser/Fiber/FiberNodeScopeResolver.php index 2beceacb7d0..3d74df8e1d6 100644 --- a/src/Analyser/Fiber/FiberNodeScopeResolver.php +++ b/src/Analyser/Fiber/FiberNodeScopeResolver.php @@ -5,7 +5,6 @@ use Fiber; use PhpParser\Node; use PhpParser\Node\Expr; -use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\MutatingScope; @@ -32,6 +31,12 @@ public function callNodeCallback( ExpressionResultStorage $storage, ): void { + if ($nodeCallback instanceof NoopNodeCallback) { + // fibers exist solely to let node callbacks ask about types, + // a noop callback does not need one + return; + } + if (Fiber::getCurrent() !== null) { $nodeCallback($node, $scope->toFiberScope()); return; @@ -115,21 +120,11 @@ protected function processPendingFibers(ExpressionResultStorage $storage): void // Process the synthetic node with a duplicated storage so that the result // computed from the asker's scope does not poison the real storage. - // Real AST nodes contained in the synthetic node already have their - // results stored and are not processed again. - $this->returnStoredExpressionResults = true; - try { - $expressionResult = $this->processExprNode( - new Node\Stmt\Expression($request->expr), - $request->expr, - $request->scope->toMutatingScope(), - $storage->duplicate(), - new NoopNodeCallback(), - ExpressionContext::createTopLevel(), - ); - } finally { - $this->returnStoredExpressionResults = false; - } + $expressionResult = $this->processExprOnDemand( + $request->expr, + $request->scope->toMutatingScope(), + $storage->duplicate(), + ); $request = $fiber->resume($expressionResult); $this->runFiberForNodeCallback($storage, $fiber, $request); diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index 30bad59a9a4..b554cc1a3dd 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -41,6 +41,8 @@ final class LazyInternalScopeFactory implements InternalScopeFactory private ?ConstantResolver $constantResolver = null; + private ExpressionResultStorageStack $expressionResultStorageStack; + private ?PhpVersion $phpVersionType = null; private ?AttributeReflectionFactory $attributeReflectionFactory = null; @@ -52,10 +54,12 @@ public function __construct( private Container $container, private $nodeCallback, private bool $fiber = false, + ?ExpressionResultStorageStack $expressionResultStorageStack = null, ) { $this->phpVersion = $this->container->getParameter('phpVersion'); $this->currentSimpleVersionParser = $this->container->getService('currentPhpVersionSimpleParser'); + $this->expressionResultStorageStack = $expressionResultStorageStack ?? new ExpressionResultStorageStack(); } public function create( @@ -105,6 +109,7 @@ public function create( $this->propertyReflectionFinder, $this->currentSimpleVersionParser, $this->constantResolver, + $this->expressionResultStorageStack, $context, $this->phpVersionType, $this->attributeReflectionFactory, @@ -130,12 +135,12 @@ public function create( public function toFiberFactory(): InternalScopeFactory { - return new self($this->container, $this->nodeCallback, true); + return new self($this->container, $this->nodeCallback, true, $this->expressionResultStorageStack); } public function toMutatingFactory(): InternalScopeFactory { - return new self($this->container, $this->nodeCallback, false); + return new self($this->container, $this->nodeCallback, false, $this->expressionResultStorageStack); } } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 26ee10770e8..f8fb44dbbc0 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -186,6 +186,7 @@ public function __construct( private PropertyReflectionFinder $propertyReflectionFinder, private Parser $parser, private ConstantResolver $constantResolver, + private ExpressionResultStorageStack $expressionResultStorageStack, protected ScopeContext $context, private PhpVersion $phpVersion, private AttributeReflectionFactory $attributeReflectionFactory, @@ -986,19 +987,71 @@ private function resolveType(string $exprString, Expr $node): Type /** @var ExprHandler $exprHandler */ foreach ($this->container->getServicesByTag(ExprHandler::EXTENSION_TAG) as $exprHandler) { - if (!$exprHandler instanceof TypeResolvingExprHandler) { - continue; - } if (!$exprHandler->supports($node)) { continue; } - return $exprHandler->resolveType($this, $node); + if ($exprHandler instanceof TypeResolvingExprHandler) { + return $exprHandler->resolveType($this, $node); + } + + return $this->resolveTypeOfNewWorldHandlerNode($node); } return new MixedType(); } + /** + * The handler of the node no longer implements TypeResolvingExprHandler. + * The answer comes from the ExpressionResult stored during the analysis + * currently in progress, or from processing the node on demand (synthetic + * nodes, or no analysis in progress at all). + * + * The scope deliberately does not reference the storage - that would create + * a reference cycle that never gets collected (see ExpressionResultStorageStack). + */ + private function resolveTypeOfNewWorldHandlerNode(Expr $node): Type + { + $storage = $this->expressionResultStorageStack->getCurrent(); + if ($storage !== null) { + $result = $storage->findExpressionResult($node); + if ($result !== null) { + if (!$result->hasTypeCallback()) { + throw new ShouldNotHappenException(sprintf( + 'ExprHandler for %s does not implement TypeResolvingExprHandler but its ExpressionResult is missing a typeCallback.', + get_class($node), + )); + } + + return $result->getTypeForScope($this); + } + } + + // a synthetic node, or no analysis in progress + $onDemandResult = $this->container->getByType(NodeScopeResolver::class)->processExprOnDemand( + $node, + $this, + $storage !== null ? $storage->duplicate() : new ExpressionResultStorage(), + ); + + return $onDemandResult->getTypeForScope($this); + } + + /** + * Makes the storage answer type questions asked on this scope (and every + * scope sharing its ExpressionResultStorageStack) for the duration of an + * analysis. The caller must pop in a finally block. + */ + public function pushExpressionResultStorage(ExpressionResultStorage $storage): void + { + $this->expressionResultStorageStack->push($storage); + } + + public function popExpressionResultStorage(): void + { + $this->expressionResultStorageStack->pop(); + } + /** * @param callable(Type): ?bool $typeCallback */ @@ -3400,6 +3453,166 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self $specifiedExpressions[$typeSpecification['exprString']] = ExpressionTypeHolder::createYes($expr, $scope->getScopeType($expr)); } + $scope = $scope->processConditionalExpressionsAfterSpecifying($specifiedExpressions); + + /** @var static */ + return $scope->scopeFactory->create( + $scope->context, + $scope->isDeclareStrictTypes(), + $scope->getFunction(), + $scope->getNamespace(), + $scope->expressionTypes, + $scope->nativeExpressionTypes, + $this->mergeConditionalExpressions($specifiedTypes->getNewConditionalExpressionHolders(), $scope->conditionalExpressions), + $scope->inClosureBindScopeClasses, + $scope->anonymousFunctionReflection, + $scope->inFirstLevelStatement, + $scope->currentlyAssignedExpressions, + $scope->currentlyAllowedUndefinedExpressions, + $scope->inFunctionCallsStack, + $scope->afterExtractCall, + $scope->parentScope, + $scope->nativeTypesPromoted, + ); + } + + /** + * New-world counterpart of filterBySpecifiedTypes. + * + * The types inside SpecifiedTypes were already computed from ExpressionResults + * by the specifyTypesCallback of an ExprHandler. This method must never call + * Scope::getType() - it only combines the given types with already-tracked + * expression type holders. + */ + public function applySpecifiedTypes(SpecifiedTypes $specifiedTypes): self + { + $typeSpecifications = []; + foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $type]) { + if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) { + continue; + } + $typeSpecifications[] = [ + 'sure' => true, + 'exprString' => (string) $exprString, + 'expr' => $expr, + 'type' => $type, + ]; + } + foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $type]) { + if ($expr instanceof Node\Scalar || $expr instanceof Array_ || $expr instanceof Expr\UnaryMinus && $expr->expr instanceof Node\Scalar) { + continue; + } + $typeSpecifications[] = [ + 'sure' => false, + 'exprString' => (string) $exprString, + 'expr' => $expr, + 'type' => $type, + ]; + } + + usort($typeSpecifications, static function (array $a, array $b): int { + $length = strlen($a['exprString']) - strlen($b['exprString']); + if ($length !== 0) { + return $length; + } + + return $b['sure'] - $a['sure']; // @phpstan-ignore minus.leftNonNumeric, minus.rightNonNumeric + }); + + $scope = $this; + $specifiedExpressions = []; + foreach ($typeSpecifications as $typeSpecification) { + $expr = $typeSpecification['expr']; + $type = $typeSpecification['type']; + $exprString = $typeSpecification['exprString']; + + if ($expr instanceof IssetExpr) { + $issetExpr = $expr; + $expr = $issetExpr->getExpr(); + + if ($typeSpecification['sure']) { + $scope = $scope->setExpressionCertainty( + $expr, + TrinaryLogic::createMaybe(), + ); + } else { + $scope = $scope->unsetExpression($expr); + } + + continue; + } + + $trackedType = null; + $trackedNativeType = null; + if (array_key_exists($exprString, $scope->expressionTypes)) { + $trackedType = $scope->expressionTypes[$exprString]->getType(); + } + if (array_key_exists($exprString, $scope->nativeExpressionTypes)) { + $trackedNativeType = $scope->nativeExpressionTypes[$exprString]->getType(); + } + + if ($typeSpecification['sure']) { + if ($specifiedTypes->shouldOverwrite()) { + $scope = $scope->assignExpression($expr, $type, $type); + } else { + $newType = $trackedType !== null ? TypeCombinator::intersect($type, $trackedType) : $type; + $newNativeType = $trackedNativeType !== null ? TypeCombinator::intersect($type, $trackedNativeType) : $type; + $scope = $scope->specifyExpressionType($expr, $newType, $newNativeType, TrinaryLogic::createYes()); + } + } else { + if ($type instanceof NeverType || $trackedType instanceof NeverType) { + continue; + } + $newType = $trackedType !== null ? TypeCombinator::remove($trackedType, $type) : null; + if ($newType === null) { + // the expression is not tracked - there is nothing to subtract from + continue; + } + $newNativeType = $trackedNativeType !== null ? TypeCombinator::remove($trackedNativeType, $type) : $newType; + $scope = $scope->specifyExpressionType($expr, $newType, $newNativeType, TrinaryLogic::createYes()); + } + + $holderType = array_key_exists($exprString, $scope->expressionTypes) + ? $scope->expressionTypes[$exprString]->getType() + : $type; + $specifiedExpressions[$exprString] = ExpressionTypeHolder::createYes($expr, $holderType); + } + + $scope = $scope->processConditionalExpressionsAfterSpecifying($specifiedExpressions); + + /** @var static */ + return $scope->scopeFactory->create( + $scope->context, + $scope->isDeclareStrictTypes(), + $scope->getFunction(), + $scope->getNamespace(), + $scope->expressionTypes, + $scope->nativeExpressionTypes, + $this->mergeConditionalExpressions($specifiedTypes->getNewConditionalExpressionHolders(), $scope->conditionalExpressions), + $scope->inClosureBindScopeClasses, + $scope->anonymousFunctionReflection, + $scope->inFirstLevelStatement, + $scope->currentlyAssignedExpressions, + $scope->currentlyAllowedUndefinedExpressions, + $scope->inFunctionCallsStack, + $scope->afterExtractCall, + $scope->parentScope, + $scope->nativeTypesPromoted, + ); + } + + /** + * Matches already-registered conditional expressions against the just-specified + * expression type holders and applies the matching consequences. + * + * Mutates and returns $this - only to be called on an intermediate scope + * that is about to be rebuilt through the scope factory. + * + * @param array $specifiedExpressions + */ + private function processConditionalExpressionsAfterSpecifying(array $specifiedExpressions): self + { + $scope = $this; $conditions = []; $originallySpecifiedExprStrings = $specifiedExpressions; $prevSpecifiedCount = -1; @@ -3478,25 +3691,7 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self } } - /** @var static */ - return $scope->scopeFactory->create( - $scope->context, - $scope->isDeclareStrictTypes(), - $scope->getFunction(), - $scope->getNamespace(), - $scope->expressionTypes, - $scope->nativeExpressionTypes, - $this->mergeConditionalExpressions($specifiedTypes->getNewConditionalExpressionHolders(), $scope->conditionalExpressions), - $scope->inClosureBindScopeClasses, - $scope->anonymousFunctionReflection, - $scope->inFirstLevelStatement, - $scope->currentlyAssignedExpressions, - $scope->currentlyAllowedUndefinedExpressions, - $scope->inFunctionCallsStack, - $scope->afterExtractCall, - $scope->parentScope, - $scope->nativeTypesPromoted, - ); + return $scope; } /** diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1321188af92..21365ced5dd 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -284,6 +284,25 @@ public function processNodes( ): void { $expressionResultStorage = new ExpressionResultStorage(); + $scope->pushExpressionResultStorage($expressionResultStorage); + try { + $this->processNodesWithStorage($nodes, $scope, $expressionResultStorage, $nodeCallback); + } finally { + $scope->popExpressionResultStorage(); + } + } + + /** + * @param Node[] $nodes + * @param callable(Node $node, Scope $scope): void $nodeCallback + */ + private function processNodesWithStorage( + array $nodes, + MutatingScope $scope, + ExpressionResultStorage $expressionResultStorage, + callable $nodeCallback, + ): void + { $alreadyTerminated = false; $exitPoints = []; @@ -499,14 +518,19 @@ public function processStmtNodes( ): StatementResult { $storage = new ExpressionResultStorage(); - return $this->processStmtNodesInternal( - $parentNode, - $stmts, - $scope, - $storage, - $nodeCallback, - $context, - )->toPublic(); + $scope->pushExpressionResultStorage($storage); + try { + return $this->processStmtNodesInternal( + $parentNode, + $stmts, + $scope, + $storage, + $nodeCallback, + $context, + )->toPublic(); + } finally { + $scope->popExpressionResultStorage(); + } } /** @@ -1425,8 +1449,13 @@ public function processStmtNode( // fresh storage - the same trait node objects are processed once per // using class and fibers must not see results from a previous pass $traitStorage = new ExpressionResultStorage(); - $this->processTraitUse($stmt, $scope, $traitStorage, $nodeCallback); - $this->processPendingFibers($traitStorage); + $scope->pushExpressionResultStorage($traitStorage); + try { + $this->processTraitUse($stmt, $scope, $traitStorage, $nodeCallback); + $this->processPendingFibers($traitStorage); + } finally { + $scope->popExpressionResultStorage(); + } // class-level node callbacks (like ClassMethodsNode) are invoked with // the outer storage but ask about expressions inside the used trait @@ -2734,6 +2763,31 @@ private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr return null; } + /** + * Processes an expression outside the normal AST traversal - e.g. a synthetic + * node a rule or extension asks about. Real AST nodes contained in it return + * their already-stored results instead of being processed again. New results + * are stored into the given storage - pass a duplicate to keep them isolated. + */ + public function processExprOnDemand(Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage): ExpressionResult + { + $this->returnStoredExpressionResults = true; + $scope->pushExpressionResultStorage($storage); + try { + return $this->processExprNode( + new Node\Stmt\Expression($expr), + $expr, + $scope, + $storage, + new NoopNodeCallback(), + ExpressionContext::createTopLevel(), + ); + } finally { + $scope->popExpressionResultStorage(); + $this->returnStoredExpressionResults = false; + } + } + /** * @param callable(Node $node, Scope $scope): void $nodeCallback */ From eba7bcfa7fafa402f568df78eeed545c416871c2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 18:46:29 +0200 Subject: [PATCH 09/22] Migrate ArrayHandler - per-item types from ExpressionResults Each item type is captured at its own evaluation point in the sequence, so [$b = 1, $b + 1, $c = $b, $c + 2, $c++, $c] infers array{1, 2, 1, 3, 1, 2} - the old world resolves all items on a single scope and cannot do this. Until the item handlers (BinaryOp, inc/dec, Assign) migrate themselves, the items resolve as their own results' before-scope evaluation instead of cascading getTypeForScope(). Narrowing stays on the fallback path - it is identical to the removed specifyDefaultTypes body. Co-Authored-By: Claude Fable 5 --- src/Analyser/ExprHandler/ArrayHandler.php | 80 ++++++++++--------- .../PHPStan/Analyser/nsrt/assign-in-array.php | 23 ++++++ 2 files changed, 67 insertions(+), 36 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/assign-in-array.php diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index 6fe74abef78..501fd5074de 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -12,13 +12,9 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; -use PHPStan\Analyser\Scope; -use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeResolvingExprHandler; -use PHPStan\Analyser\TypeSpecifier; -use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\LiteralArrayItem; use PHPStan\Node\LiteralArrayNode; @@ -26,14 +22,16 @@ use PHPStan\Type\CallableType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use function array_key_exists; use function array_merge; use function count; +use function spl_object_id; /** - * @implements TypeResolvingExprHandler + * @implements ExprHandler */ #[AutowiredService] -final class ArrayHandler implements TypeResolvingExprHandler +final class ArrayHandler implements ExprHandler { public function __construct( @@ -48,34 +46,11 @@ public function supports(Expr $expr): bool return $expr instanceof Array_; } - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - $type = $this->initializerExprTypeResolver->getArrayType($expr, static fn (Expr $expr): Type => $scope->getType($expr)); - - if ( - count($expr->items) === 2 - && isset($expr->items[0], $expr->items[1]) - && $type->isCallable()->maybe() - ) { - $isCallableCall = new FuncCall( - new FullyQualified('is_callable'), - [new Arg($expr)], - ); - if ( - $scope->hasExpressionType($isCallableCall)->yes() - && $scope->getType($isCallableCall)->isTrue()->yes() - ) { - $type = TypeCombinator::intersect($type, new CallableType()); - } - } - - return $type; - } - public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { $beforeScope = $scope; $itemNodes = []; + $itemResults = []; $hasYield = false; $throwPoints = []; $impurePoints = []; @@ -85,6 +60,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeScopeResolver->callNodeCallback($nodeCallback, $arrayItem, $scope, $storage); if ($arrayItem->key !== null) { $keyResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->key, $scope, $storage, $nodeCallback, $context->enterDeep()); + $itemResults[spl_object_id($arrayItem->key)] = $keyResult; $hasYield = $hasYield || $keyResult->hasYield(); $throwPoints = array_merge($throwPoints, $keyResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints()); @@ -93,6 +69,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } $valueResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->value, $scope, $storage, $nodeCallback, $context->enterDeep()); + $itemResults[spl_object_id($arrayItem->value)] = $valueResult; $hasYield = $hasYield || $valueResult->hasYield(); $throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); @@ -109,12 +86,43 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - ); - } + typeCallback: function (MutatingScope $s) use ($expr, $itemResults): Type { + // each item type was captured at its own evaluation point in the + // sequence - resolving all items on any single scope (the old world) + // cannot handle items with side effects like [$b = 1, $b + 1, $b++] + $type = $this->initializerExprTypeResolver->getArrayType($expr, static function (Expr $inner) use ($itemResults, $s): Type { + $id = spl_object_id($inner); + if (array_key_exists($id, $itemResults)) { + return $s->nativeTypesPromoted + ? $itemResults[$id]->getNativeType() + : $itemResults[$id]->getType(); + } - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); + // getArrayType only asks about item keys and values - guarded + // legacy bridge just in case + return $s->getType($inner); + }); + + if ( + count($expr->items) === 2 + && isset($expr->items[0], $expr->items[1]) + && $type->isCallable()->maybe() + ) { + $isCallableCall = new FuncCall( + new FullyQualified('is_callable'), + [new Arg($expr)], + ); + if ( + $s->hasExpressionType($isCallableCall)->yes() + && $s->getType($isCallableCall)->isTrue()->yes() + ) { + $type = TypeCombinator::intersect($type, new CallableType()); + } + } + + return $type; + }, + ); } } diff --git a/tests/PHPStan/Analyser/nsrt/assign-in-array.php b/tests/PHPStan/Analyser/nsrt/assign-in-array.php new file mode 100644 index 00000000000..955df512571 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/assign-in-array.php @@ -0,0 +1,23 @@ + Date: Fri, 12 Jun 2026 18:48:20 +0200 Subject: [PATCH 10/22] Throw on unbalanced ExpressionResultStorageStack pop An unbalanced push/pop is the one way the ambient storage design can still be misused - fail immediately instead of silently answering later type questions from the wrong storage. Co-Authored-By: Claude Fable 5 --- src/Analyser/ExpressionResultStorageStack.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Analyser/ExpressionResultStorageStack.php b/src/Analyser/ExpressionResultStorageStack.php index 3bf644f7965..9d0e37f4e71 100644 --- a/src/Analyser/ExpressionResultStorageStack.php +++ b/src/Analyser/ExpressionResultStorageStack.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +use PHPStan\ShouldNotHappenException; use function array_pop; use function count; @@ -37,6 +38,10 @@ public function push(ExpressionResultStorage $storage): void public function pop(): void { + if (count($this->stack) === 0) { + throw new ShouldNotHappenException('Unbalanced ExpressionResultStorageStack pop.'); + } + array_pop($this->stack); } From 43bdaa4c71086067525d89f7eb0fcb2370729992 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 19:19:11 +0200 Subject: [PATCH 11/22] Fix PHP 7.4 compat --- src/Analyser/ExpressionResultStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/ExpressionResultStorage.php b/src/Analyser/ExpressionResultStorage.php index b04bfb507e2..10c14749296 100644 --- a/src/Analyser/ExpressionResultStorage.php +++ b/src/Analyser/ExpressionResultStorage.php @@ -51,7 +51,7 @@ public function storeExpressionResult(Expr $expr, ExpressionResult $expressionRe public function findExpressionResult(Expr $expr): ?ExpressionResult { - return $this->exprResults[$expr] ?? $this->fallback?->findExpressionResult($expr); + return $this->exprResults[$expr] ?? ($this->fallback !== null ? $this->fallback->findExpressionResult($expr) : null); } } From 342272de05e5a7e6eb835490bddc8f21793e541f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 19:35:07 +0200 Subject: [PATCH 12/22] Migrate VariableHandler and InstanceofHandler - narrowing from ExpressionResults Both handlers stop implementing TypeResolvingExprHandler and wire typeCallback + specifyTypesCallback, so their truthy/falsey scopes flow through MutatingScope::applySpecifiedTypes - the first real exercise of the new-world narrowing machinery. The old-world TypeSpecifier dispatcher answers nodes of converted handlers from the stored ExpressionResult (MutatingScope::specifyTypesOfNewWorldHandlerNode), processes synthetic nodes on demand (the 'foo' === $a::class rewrite builds synthetic Instanceof_ nodes), and falls back to specifyDefaultTypes when the result carries no callback - which is exactly what such handlers used to implement. Exercising the machinery surfaced two gaps in applySpecifiedTypes: - Expressions not tracked in the scope lost their sureNot narrowing ($var->name instanceof Identifier ? ... : ... stopped narrowing the else branch). The current type to intersect with or subtract from is now priced from the stored ExpressionResult (getCurrentTypesOfSpecifiedExpr) instead of Scope::getType(). - Only Yes-certainty holders hold the current type of an expression. A Maybe-certainty holder holds the when-defined type (falsy after an or-merge in nsrt/bug-pr-339.php), which the certainty-aware Scope::getType() never returned - narrowing against it produced NeverType and mis-fired conditional expression holders. AssignHandler's placeholder result for an assignment target now carries VariableHandler::createTypeCallback - every stored result for a converted handler's node type must answer type questions itself. Co-Authored-By: Claude Fable 5 --- src/Analyser/ExprHandler/AssignHandler.php | 3 + .../ExprHandler/InstanceofHandler.php | 193 +++++++++--------- src/Analyser/ExprHandler/VariableHandler.php | 92 +++++---- src/Analyser/ExpressionResult.php | 14 ++ src/Analyser/MutatingScope.php | 83 +++++++- src/Analyser/TypeSpecifier.php | 16 +- 6 files changed, 262 insertions(+), 139 deletions(-) diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index e0db4837ddc..9ce7f6739bd 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -420,6 +420,9 @@ public function processAssignVar( isAlwaysTerminating: false, throwPoints: [], impurePoints: [], + // VariableHandler no longer implements TypeResolvingExprHandler - + // type questions about the target node are answered from this result + typeCallback: $var instanceof Variable ? VariableHandler::createTypeCallback($var) : null, )); $nodeScopeResolver->callNodeCallback($nodeCallback, $var, $enterExpressionAssign ? $scope->enterExpressionAssign($var) : $scope, $storage); $hasYield = false; diff --git a/src/Analyser/ExprHandler/InstanceofHandler.php b/src/Analyser/ExprHandler/InstanceofHandler.php index 885a418f846..ff8bda6c552 100644 --- a/src/Analyser/ExprHandler/InstanceofHandler.php +++ b/src/Analyser/ExprHandler/InstanceofHandler.php @@ -10,11 +10,10 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; -use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -33,13 +32,16 @@ use function strtolower; /** - * @implements TypeResolvingExprHandler + * @implements ExprHandler */ #[AutowiredService] -final class InstanceofHandler implements TypeResolvingExprHandler +final class InstanceofHandler implements ExprHandler { - public function __construct(private ExpressionResultFactory $expressionResultFactory) + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + private TypeSpecifier $typeSpecifier, + ) { } @@ -57,6 +59,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = $exprResult->getImpurePoints(); $isAlwaysTerminating = $exprResult->isAlwaysTerminating(); $scope = $exprResult->getScope(); + $classResult = null; if (!$expr->class instanceof Name) { $classResult = $nodeScopeResolver->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $classResult->getScope(); @@ -74,104 +77,106 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints, - ); - } - - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - $expressionType = $scope->getType($expr->expr); - if ( - $scope->isInTrait() - && TypeUtils::findThisType($expressionType) !== null - ) { - return new BooleanType(); - } - if ($expressionType instanceof NeverType) { - return new ConstantBooleanType(false); - } - - $uncertainty = false; - - if ($expr->class instanceof Name) { - $unresolvedClassName = $expr->class->toString(); - if ( - strtolower($unresolvedClassName) === 'static' - && $scope->isInClass() - ) { - $classType = new StaticType($scope->getClassReflection()); - } else { - $className = $scope->resolveName($expr->class); - $classType = new ObjectType($className); - } - } else { - $result = $scope->getType($expr->class)->toObjectTypeForInstanceofCheck(); - $classType = $result->type; - $uncertainty = $result->uncertainty; - } + typeCallback: static function (MutatingScope $s) use ($expr, $exprResult, $classResult): Type { + $expressionType = $exprResult->getTypeForScope($s); + if ( + $s->isInTrait() + && TypeUtils::findThisType($expressionType) !== null + ) { + return new BooleanType(); + } + if ($expressionType instanceof NeverType) { + return new ConstantBooleanType(false); + } - if ($classType->isSuperTypeOf(new MixedType())->yes()) { - return new BooleanType(); - } + $uncertainty = false; + + if ($expr->class instanceof Name) { + $unresolvedClassName = $expr->class->toString(); + if ( + strtolower($unresolvedClassName) === 'static' + && $s->isInClass() + ) { + $classType = new StaticType($s->getClassReflection()); + } else { + $className = $s->resolveName($expr->class); + $classType = new ObjectType($className); + } + } else { + $classNameType = $classResult !== null + ? $classResult->getTypeForScope($s) + : $s->getType($expr->class); + $result = $classNameType->toObjectTypeForInstanceofCheck(); + $classType = $result->type; + $uncertainty = $result->uncertainty; + } - $isSuperType = $classType->isSuperTypeOf($expressionType); + if ($classType->isSuperTypeOf(new MixedType())->yes()) { + return new BooleanType(); + } - if ($isSuperType->no()) { - return new ConstantBooleanType(false); - } elseif ($isSuperType->yes() && !$uncertainty) { - return new ConstantBooleanType(true); - } + $isSuperType = $classType->isSuperTypeOf($expressionType); - return new BooleanType(); - } + if ($isSuperType->no()) { + return new ConstantBooleanType(false); + } elseif ($isSuperType->yes() && !$uncertainty) { + return new ConstantBooleanType(true); + } - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - $exprNode = $expr->expr; - if ($expr->class instanceof Name) { - $className = (string) $expr->class; - $lowercasedClassName = strtolower($className); - if ($lowercasedClassName === 'self' && $scope->isInClass()) { - $type = new ObjectType($scope->getClassReflection()->getName()); - } elseif ($lowercasedClassName === 'static' && $scope->isInClass()) { - $type = new StaticType($scope->getClassReflection()); - } elseif ($lowercasedClassName === 'parent') { - if ( - $scope->isInClass() - && $scope->getClassReflection()->getParentClass() !== null - ) { - $type = new ObjectType($scope->getClassReflection()->getParentClass()->getName()); - } else { - $type = new NonexistentParentClassType(); + return new BooleanType(); + }, + specifyTypesCallback: function (MutatingScope $s, TypeSpecifierContext $context) use ($expr, $exprResult, $classResult): SpecifiedTypes { + $exprNode = $expr->expr; + if ($expr->class instanceof Name) { + $className = (string) $expr->class; + $lowercasedClassName = strtolower($className); + if ($lowercasedClassName === 'self' && $s->isInClass()) { + $type = new ObjectType($s->getClassReflection()->getName()); + } elseif ($lowercasedClassName === 'static' && $s->isInClass()) { + $type = new StaticType($s->getClassReflection()); + } elseif ($lowercasedClassName === 'parent') { + if ( + $s->isInClass() + && $s->getClassReflection()->getParentClass() !== null + ) { + $type = new ObjectType($s->getClassReflection()->getParentClass()->getName()); + } else { + $type = new NonexistentParentClassType(); + } + } else { + $type = new ObjectType($className); + } + return $this->typeSpecifier->create($exprNode, $type, $context, $s)->setRootExpr($expr); } - } else { - $type = new ObjectType($className); - } - return $typeSpecifier->create($exprNode, $type, $context, $scope)->setRootExpr($expr); - } - $result = $scope->getType($expr->class)->toObjectTypeForInstanceofCheck(); - $type = $result->type; - $uncertainty = $result->uncertainty; - - if (!$type->isSuperTypeOf(new MixedType())->yes()) { - if ($context->true()) { - $type = TypeCombinator::intersect( - $type, - new ObjectWithoutClassType(), - ); - return $typeSpecifier->create($exprNode, $type, $context, $scope)->setRootExpr($expr); - } elseif ($context->false() && !$uncertainty) { - $exprType = $scope->getType($expr->expr); - if (!$type->isSuperTypeOf($exprType)->yes()) { - return $typeSpecifier->create($exprNode, $type, $context, $scope)->setRootExpr($expr); + $classNameType = $classResult !== null + ? $classResult->getTypeForScope($s) + : $s->getType($expr->class); + $result = $classNameType->toObjectTypeForInstanceofCheck(); + $type = $result->type; + $uncertainty = $result->uncertainty; + + if (!$type->isSuperTypeOf(new MixedType())->yes()) { + if ($context->true()) { + $type = TypeCombinator::intersect( + $type, + new ObjectWithoutClassType(), + ); + return $this->typeSpecifier->create($exprNode, $type, $context, $s)->setRootExpr($expr); + } elseif ($context->false() && !$uncertainty) { + $exprType = $exprResult->getTypeForScope($s); + if (!$type->isSuperTypeOf($exprType)->yes()) { + return $this->typeSpecifier->create($exprNode, $type, $context, $s)->setRootExpr($expr); + } + } + } + if ($context->true()) { + return $this->typeSpecifier->create($exprNode, new ObjectWithoutClassType(), $context, $s)->setRootExpr($exprNode); } - } - } - if ($context->true()) { - return $typeSpecifier->create($exprNode, new ObjectWithoutClassType(), $context, $scope)->setRootExpr($exprNode); - } - return (new SpecifiedTypes([], []))->setRootExpr($expr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); + }, + ); } } diff --git a/src/Analyser/ExprHandler/VariableHandler.php b/src/Analyser/ExprHandler/VariableHandler.php index 70ce8439856..1fc9923355f 100644 --- a/src/Analyser/ExprHandler/VariableHandler.php +++ b/src/Analyser/ExprHandler/VariableHandler.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser\ExprHandler; +use Closure; use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\Variable; @@ -11,12 +12,12 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeResolvingExprHandler; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; @@ -29,13 +30,16 @@ use function is_string; /** - * @implements TypeResolvingExprHandler + * @implements ExprHandler */ #[AutowiredService] -final class VariableHandler implements TypeResolvingExprHandler +final class VariableHandler implements ExprHandler { - public function __construct(private ExpressionResultFactory $expressionResultFactory) + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + private TypeSpecifier $typeSpecifier, + ) { } @@ -44,36 +48,49 @@ public function supports(Expr $expr): bool return $expr instanceof Variable; } - public function resolveType(MutatingScope $scope, Expr $expr): Type + /** + * Evaluates the variable as a read on the asking scope. Also used by + * AssignHandler for the placeholder result it stores for an assignment + * target - every stored result for a Variable node must carry a + * typeCallback now that this handler no longer implements + * TypeResolvingExprHandler. + * + * @return Closure(MutatingScope): Type + */ + public static function createTypeCallback(Variable $expr, ?ExpressionResult $nameResult = null): Closure { - if (is_string($expr->name)) { - if ($scope->hasVariableType($expr->name)->no()) { - return new ErrorType(); + return static function (MutatingScope $s) use ($expr, $nameResult): Type { + if (is_string($expr->name)) { + if ($s->hasVariableType($expr->name)->no()) { + return new ErrorType(); + } + + return $s->getVariableType($expr->name); } - return $scope->getVariableType($expr->name); - } + $nameType = $nameResult !== null + ? $nameResult->getTypeForScope($s) + : $s->getType($expr->name); + if (count($nameType->getConstantStrings()) > 0) { + $types = []; + foreach ($nameType->getConstantStrings() as $constantString) { + $variableScope = $s + ->filterByTruthyValue( + new Identical($expr->name, new String_($constantString->getValue())), + ); + if ($variableScope->hasVariableType($constantString->getValue())->no()) { + $types[] = new ErrorType(); + continue; + } - $nameType = $scope->getType($expr->name); - if (count($nameType->getConstantStrings()) > 0) { - $types = []; - foreach ($nameType->getConstantStrings() as $constantString) { - $variableScope = $scope - ->filterByTruthyValue( - new Identical($expr->name, new String_($constantString->getValue())), - ); - if ($variableScope->hasVariableType($constantString->getValue())->no()) { - $types[] = new ErrorType(); - continue; + $types[] = $variableScope->getVariableType($constantString->getValue()); } - $types[] = $variableScope->getVariableType($constantString->getValue()); + return TypeCombinator::union(...$types); } - return TypeCombinator::union(...$types); - } - - return new MixedType(); + return new MixedType(); + }; } public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult @@ -83,6 +100,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $throwPoints = []; $impurePoints = []; $isAlwaysTerminating = false; + $nameResult = null; if (is_string($expr->name)) { if (in_array($expr->name, Scope::SUPERGLOBAL_VARIABLES, true)) { $impurePoints[] = new ImpurePoint($scope, $expr, 'superglobal', 'access to superglobal variable', true); @@ -95,22 +113,18 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $nameResult->isAlwaysTerminating(); $scope = $nameResult->getScope(); } + return $this->expressionResultFactory->create( $scope, - $beforeScope, - $expr, - $hasYield, - $isAlwaysTerminating, - $throwPoints, - $impurePoints, - static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - static fn (): MutatingScope => $scope->filterByFalseyValue($expr), + beforeScope: $beforeScope, + expr: $expr, + hasYield: $hasYield, + isAlwaysTerminating: $isAlwaysTerminating, + throwPoints: $throwPoints, + impurePoints: $impurePoints, + typeCallback: self::createTypeCallback($expr, $nameResult), + specifyTypesCallback: fn (MutatingScope $s, TypeSpecifierContext $context): SpecifiedTypes => $this->typeSpecifier->specifyDefaultTypes($s, $expr, $context), ); } - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); - } - } diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 9128ea6d2e0..411e1edeefc 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -175,6 +175,20 @@ public function hasTypeCallback(): bool return $this->typeCallback !== null; } + /** + * Re-evaluates the narrowing on a different scope (e.g. the one an old-world + * caller holds). Returns null when the handler wired no specifyTypesCallback - + * the caller falls back to default truthy/falsey narrowing. + */ + public function getSpecifiedTypesForScope(MutatingScope $scope, TypeSpecifierContext $context): ?SpecifiedTypes + { + if ($this->specifyTypesCallback === null) { + return null; + } + + return ($this->specifyTypesCallback)($scope, $context); + } + /** * Re-evaluates the expression type on a different scope (e.g. a narrowed one). * Unlike getType(), the result is not cached. diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index f8fb44dbbc0..5f66f52e259 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1037,6 +1037,64 @@ private function resolveTypeOfNewWorldHandlerNode(Expr $node): Type return $onDemandResult->getTypeForScope($this); } + /** + * Prices the current (phpdoc, native) type pair of an expression that + * applySpecifiedTypes() needs to intersect with or subtract from but that + * is not tracked in the scope. Old-world filterBySpecifiedTypes() asked + * Scope::getType() here; pricing from the stored ExpressionResult answers + * through the typeCallback for converted handlers and keeps the legacy + * resolution as a bridge for the rest. Returns null for nodes the analysis + * in progress never processed (synthetic ones). + * + * @return array{Type, Type}|null + */ + private function getCurrentTypesOfSpecifiedExpr(Expr $expr): ?array + { + $storage = $this->expressionResultStorageStack->getCurrent(); + if ($storage === null) { + return null; + } + + $result = $storage->findExpressionResult($expr); + if ($result === null) { + return null; + } + + return [ + $result->getTypeForScope($this), + $result->getTypeForScope($this->promoteNativeTypes()), + ]; + } + + /** + * Narrowing counterpart of resolveTypeOfNewWorldHandlerNode() - the old-world + * TypeSpecifier dispatcher asks here for nodes whose handler no longer + * implements TypeResolvingExprHandler. Returns null when the ExpressionResult + * carries no specifyTypesCallback - the dispatcher falls back to default + * truthy/falsey narrowing, which is what such handlers used to implement. + * + * @internal + */ + public function specifyTypesOfNewWorldHandlerNode(Expr $node, TypeSpecifierContext $context): ?SpecifiedTypes + { + $storage = $this->expressionResultStorageStack->getCurrent(); + if ($storage !== null) { + $result = $storage->findExpressionResult($node); + if ($result !== null) { + return $result->getSpecifiedTypesForScope($this, $context); + } + } + + // a synthetic node, or no analysis in progress + $onDemandResult = $this->container->getByType(NodeScopeResolver::class)->processExprOnDemand( + $node, + $this, + $storage !== null ? $storage->duplicate() : new ExpressionResultStorage(), + ); + + return $onDemandResult->getSpecifiedTypesForScope($this, $context); + } + /** * Makes the storage answer type questions asked on this scope (and every * scope sharing its ExpressionResultStorageStack) for the duration of an @@ -3542,14 +3600,35 @@ public function applySpecifiedTypes(SpecifiedTypes $specifiedTypes): self continue; } + // only Yes-certainty holders hold the current type of the expression - + // a Maybe-certainty holder holds the when-defined type (e.g. after + // merging a branch where the expression was never assigned), which + // the certainty-aware Scope::getType() of the old world never returned $trackedType = null; $trackedNativeType = null; - if (array_key_exists($exprString, $scope->expressionTypes)) { + if ( + array_key_exists($exprString, $scope->expressionTypes) + && $scope->expressionTypes[$exprString]->getCertainty()->yes() + ) { $trackedType = $scope->expressionTypes[$exprString]->getType(); } - if (array_key_exists($exprString, $scope->nativeExpressionTypes)) { + if ( + array_key_exists($exprString, $scope->nativeExpressionTypes) + && $scope->nativeExpressionTypes[$exprString]->getCertainty()->yes() + ) { $trackedNativeType = $scope->nativeExpressionTypes[$exprString]->getType(); } + if ($trackedType === null) { + $currentTypes = $scope->getCurrentTypesOfSpecifiedExpr($expr); + if ($currentTypes !== null) { + if ($scope->isComplexUnionType($currentTypes[0])) { + continue; + } + + $trackedType = $currentTypes[0]; + $trackedNativeType ??= $currentTypes[1]; + } + } if ($typeSpecification['sure']) { if ($specifiedTypes->shouldOverwrite()) { diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index dd1982e6694..b91b62fb2e0 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -91,14 +91,22 @@ public function specifyTypesInCondition( /** @var ExprHandler $exprHandler */ foreach ($this->container->getServicesByTag(ExprHandler::EXTENSION_TAG) as $exprHandler) { - if (!$exprHandler instanceof TypeResolvingExprHandler) { - continue; - } if (!$exprHandler->supports($expr)) { continue; } - return $exprHandler->specifyTypes($this, $scope, $expr, $context); + if ($exprHandler instanceof TypeResolvingExprHandler) { + return $exprHandler->specifyTypes($this, $scope, $expr, $context); + } + + if ($scope instanceof MutatingScope) { + $specifiedTypes = $scope->specifyTypesOfNewWorldHandlerNode($expr, $context); + if ($specifiedTypes !== null) { + return $specifiedTypes; + } + } + + break; } return $this->specifyDefaultTypes($scope, $expr, $context); From d7b5a5fce051558e68c2cfb1e89292605cd75701 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 19:39:49 +0200 Subject: [PATCH 13/22] Add regression tests for evaluation-point array item types Closes https://github.com/phpstan/phpstan/issues/13944 Closes https://github.com/phpstan/phpstan/issues/12207 Closes https://github.com/phpstan/phpstan/issues/7155 Co-Authored-By: Claude Fable 5 --- tests/PHPStan/Analyser/nsrt/bug-12207.php | 31 +++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-13944.php | 48 +++++++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-7155.php | 16 ++++++++ 3 files changed, 95 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12207.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-13944.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-7155.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-12207.php b/tests/PHPStan/Analyser/nsrt/bug-12207.php new file mode 100644 index 00000000000..63990d759c4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12207.php @@ -0,0 +1,31 @@ +}> + */ + public function bar(): Generator + { + yield 'foo' => [ + $a = 'string', + ['string' => $a], + ]; + } + + public function baz(): void + { + $value = [ + $a = 'string', + ['string' => $a], + ]; + assertType("array{'string', array{string: 'string'}}", $value); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-13944.php b/tests/PHPStan/Analyser/nsrt/bug-13944.php new file mode 100644 index 00000000000..ed1aeaf31b0 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13944.php @@ -0,0 +1,48 @@ +, + * "when@stage"?: array, + * } $config + */ +function config(array $config): void +{ +} + +config([ + 'when@dev' => $does_not_work = [ + 'controllers' => [ + 'resource' => 'routing.controllers', + ], + ], + 'when@stage' => $does_not_work, +]); + +assertType("array{'when@dev': array{controllers: array{resource: 'routing.controllers'}}, 'when@stage': array{controllers: array{resource: 'routing.controllers'}}}", [ + 'when@dev' => $does_not_work, + 'when@stage' => $does_not_work, +]); + +assertType("array{'when@dev': array{controllers: array{resource: 'routing.controllers'}}, 'when@stage': array{controllers: array{resource: 'routing.controllers'}}}", [ + 'when@dev' => $defined_inside = [ + 'controllers' => [ + 'resource' => 'routing.controllers', + ], + ], + 'when@stage' => $defined_inside, +]); + +$does_work = [ + 'controllers' => [ + 'resource' => 'routing.controllers', + ], +]; +config([ + 'when@dev' => $does_work, + 'when@stage' => $does_work, +]); diff --git a/tests/PHPStan/Analyser/nsrt/bug-7155.php b/tests/PHPStan/Analyser/nsrt/bug-7155.php new file mode 100644 index 00000000000..13fc56e4830 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7155.php @@ -0,0 +1,16 @@ + Date: Fri, 12 Jun 2026 19:49:13 +0200 Subject: [PATCH 14/22] Add regression test for certainty of undefined variables in loops Closes https://github.com/phpstan/phpstan/issues/2032 Co-Authored-By: Claude Fable 5 --- .../Variables/DefinedVariableRuleTest.php | 18 ++++++++++++++++++ .../PHPStan/Rules/Variables/data/bug-2032.php | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-2032.php diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 3747fca81ab..61aa2bffdc0 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1674,4 +1674,22 @@ public function testBug10090(): void ]); } + public function testBug2032(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-2032.php'], [ + [ + 'Undefined variable: $undefined', + 6, + ], + [ + 'Undefined variable: $undefined', + 15, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-2032.php b/tests/PHPStan/Rules/Variables/data/bug-2032.php new file mode 100644 index 00000000000..65e9d1c5f62 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-2032.php @@ -0,0 +1,17 @@ + Date: Fri, 12 Jun 2026 20:01:15 +0200 Subject: [PATCH 15/22] Store expressions even without FNSR --- src/Analyser/Fiber/FiberNodeScopeResolver.php | 2 +- src/Analyser/MutatingScope.php | 10 ++++++++++ src/Analyser/NodeScopeResolver.php | 3 +++ .../Rules/Variables/DefinedVariableRuleTest.php | 4 ++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Analyser/Fiber/FiberNodeScopeResolver.php b/src/Analyser/Fiber/FiberNodeScopeResolver.php index 3d74df8e1d6..e49935f0023 100644 --- a/src/Analyser/Fiber/FiberNodeScopeResolver.php +++ b/src/Analyser/Fiber/FiberNodeScopeResolver.php @@ -58,7 +58,7 @@ public function callNodeCallback( public function storeExpressionResult(ExpressionResultStorage $storage, Expr $expr, ExpressionResult $expressionResult): void { - $storage->storeExpressionResult($expr, $expressionResult); + parent::storeExpressionResult($storage, $expr, $expressionResult); $this->processPendingFibersForRequestedExpr($storage, $expr, $expressionResult); } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5f66f52e259..3eeedd7e457 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3499,6 +3499,11 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self continue; } + if ($expr instanceof Variable && is_string($expr->name) && $scope->hasVariableType($expr->name)->no()) { + // testing a certainly-undefined variable cannot make it defined + continue; + } + if ($typeSpecification['sure']) { if ($specifiedTypes->shouldOverwrite()) { $scope = $scope->assignExpression($expr, $type, $type); @@ -3600,6 +3605,11 @@ public function applySpecifiedTypes(SpecifiedTypes $specifiedTypes): self continue; } + if ($expr instanceof Variable && is_string($expr->name) && $scope->hasVariableType($expr->name)->no()) { + // testing a certainly-undefined variable cannot make it defined + continue; + } + // only Yes-certainty holders hold the current type of the expression - // a Maybe-certainty holder holds the when-defined type (e.g. after // merging a branch where the expression was never assigned), which diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 21365ced5dd..4cb970ac163 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -382,6 +382,9 @@ private function processNodesWithStorage( public function storeExpressionResult(ExpressionResultStorage $storage, Expr $expr, ExpressionResult $expressionResult): void { + // converted handlers (no TypeResolvingExprHandler) are answered from + // stored results in both worlds - storing must not depend on fibers + $storage->storeExpressionResult($expr, $expressionResult); } protected function processPendingFibers(ExpressionResultStorage $storage): void diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 61aa2bffdc0..1a8a6c637e2 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1685,6 +1685,10 @@ public function testBug2032(): void 'Undefined variable: $undefined', 6, ], + [ + 'Undefined variable: $undefined', + 9, + ], [ 'Undefined variable: $undefined', 15, From 1731073be014017afeb67828a473b974d5ae54df Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 20:07:57 +0200 Subject: [PATCH 16/22] Only sureNot specifications skip certainly-undefined variables A sure specification (e.g. is_string($a)) can only hold for a defined variable, so it still makes the variable defined inside the branch - one error at the test site, no cascade. Removing a type from a certainly-undefined variable proves nothing about its definedness. Co-Authored-By: Claude Fable 5 --- src/Analyser/MutatingScope.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 3eeedd7e457..6cef5b33160 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3499,8 +3499,14 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self continue; } - if ($expr instanceof Variable && is_string($expr->name) && $scope->hasVariableType($expr->name)->no()) { - // testing a certainly-undefined variable cannot make it defined + if ( + !$typeSpecification['sure'] + && $expr instanceof Variable && is_string($expr->name) + && $scope->hasVariableType($expr->name)->no() + ) { + // removing type from a certainly-undefined variable cannot make + // it defined; a sure specification (e.g. is_string($a)) still can - + // the condition can only hold for a defined variable continue; } @@ -3605,8 +3611,14 @@ public function applySpecifiedTypes(SpecifiedTypes $specifiedTypes): self continue; } - if ($expr instanceof Variable && is_string($expr->name) && $scope->hasVariableType($expr->name)->no()) { - // testing a certainly-undefined variable cannot make it defined + if ( + !$typeSpecification['sure'] + && $expr instanceof Variable && is_string($expr->name) + && $scope->hasVariableType($expr->name)->no() + ) { + // removing type from a certainly-undefined variable cannot make + // it defined; a sure specification (e.g. is_string($a)) still can - + // the condition can only hold for a defined variable continue; } From 630fa91d6ae535e0660878e97d84372e8afd7e27 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 21:49:02 +0200 Subject: [PATCH 17/22] This is better --- src/Analyser/MutatingScope.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 6cef5b33160..0b9abdf3032 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1061,8 +1061,8 @@ private function getCurrentTypesOfSpecifiedExpr(Expr $expr): ?array } return [ - $result->getTypeForScope($this), - $result->getTypeForScope($this->promoteNativeTypes()), + $result->getType(), + $result->getNativeType(), ]; } From dd44b8719c4ec3550679848b951388f2444ab2a1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 12 Jun 2026 23:00:24 +0200 Subject: [PATCH 18/22] ExpressionResult::createTypesCallback - the inside-out TypeSpecifier::create() How a type constraint on a node translates into narrowing entries is the producing handler's knowledge, declared on its ExpressionResult - never re-derived by unwrapping the AST elsewhere. DefaultNarrowingHelper (recreated from the first rewrite attempt) carries the default boolean-context narrowing (one sureNot entry, no type ask, no nullsafe chain-walking) and createSubjectTypes(): ask the subject result's createTypesCallback, fall back to a single sure/sureNot entry for the subject node. No purity gates, no structural unwrapping. AssignHandler fans a type constraint out to the assigned variable and the assigned expression - nested assignments compose through the assigned expression's own result, which is what will delete unwrapAssign. CoalesceHandler delegates to its left side when the type rules the right side in or out, so ($e ?? null) instanceof Foo narrows $e. AssignHandler also wires specifyTypesCallback: the assigned variable narrows by the boolean outcome, plus the $arr[$key] inference after $key = array_key_first/array_key_last/array_search/array_find_key. The null-context inferences stay in the old-world specifyTypes() - result-based asks are always truthy or falsey. specifyTypesCallbacks no longer touch TypeSpecifier: VariableHandler uses DefaultNarrowingHelper, InstanceofHandler narrows its subject through createSubjectTypes(). TypeSpecifierTest's assign-in-instanceof expectations hold unchanged - the new channel reproduces create()'s emission exactly. Co-Authored-By: Claude Fable 5 --- src/Analyser/ExprHandler/AssignHandler.php | 148 +++++++++++++++++- src/Analyser/ExprHandler/CoalesceHandler.php | 18 +++ .../Helper/DefaultNarrowingHelper.php | 91 +++++++++++ .../ExprHandler/InstanceofHandler.php | 12 +- src/Analyser/ExprHandler/VariableHandler.php | 6 +- src/Analyser/ExpressionResult.php | 25 +++ src/Analyser/ExpressionResultFactory.php | 2 + 7 files changed, 292 insertions(+), 10 deletions(-) create mode 100644 src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 9ce7f6739bd..362a9619126 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -28,6 +28,7 @@ use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExpressionTypeHolder; +use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; @@ -97,6 +98,7 @@ public function __construct( private ExprPrinter $exprPrinter, private MatchHandler $matchHandler, private ExpressionResultFactory $expressionResultFactory, + private DefaultNarrowingHelper $defaultNarrowingHelper, ) { } @@ -292,6 +294,7 @@ public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $e public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { $beforeScope = $scope; + $assignedExprResult = null; $result = $this->processAssignVar( $nodeScopeResolver, $scope, @@ -301,7 +304,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $expr->expr, $nodeCallback, $context, - function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $storage, $nodeScopeResolver): ExpressionResult { + function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $storage, $nodeScopeResolver, &$assignedExprResult): ExpressionResult { $beforeScope = $scope; $impurePoints = []; if ($expr instanceof AssignRef) { @@ -331,6 +334,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto } $result = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); + $assignedExprResult = $result; $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -391,9 +395,151 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto isAlwaysTerminating: $result->isAlwaysTerminating(), throwPoints: $result->getThrowPoints(), impurePoints: $result->getImpurePoints(), + specifyTypesCallback: $expr instanceof Assign ? $this->createSpecifyTypesCallback($expr) : null, + createTypesCallback: $expr instanceof Assign ? $this->createCreateTypesCallback($expr, $assignedExprResult) : null, ); } + /** + * A type constraint on an assignment constrains the assigned variable + * and the assigned expression - what TypeSpecifier::create() recovered + * by unwrapping assign chains. Nested assignments compose through the + * assigned expression's own result. + * + * @return Closure(MutatingScope, Type, TypeSpecifierContext): SpecifiedTypes + */ + private function createCreateTypesCallback(Assign $expr, ?ExpressionResult $assignedExprResult): Closure + { + return function (MutatingScope $s, Type $type, TypeSpecifierContext $context) use ($expr, $assignedExprResult): SpecifiedTypes { + $types = $this->defaultNarrowingHelper->createSubjectTypes($s, $expr->var, null, $type, $context); + + return $types->unionWith( + $this->defaultNarrowingHelper->createSubjectTypes($s, $expr->expr, $assignedExprResult, $type, $context), + ); + }; + } + + /** + * New-world copy of the non-null contexts of specifyTypes(): the assigned + * variable narrows by the boolean outcome, plus the $arr[$key] inference + * after $key = array_key_first/array_key_last/array_search/array_find_key. + * The null-context inferences stay in specifyTypes() - result-based asks + * are always truthy or falsey. + * + * @return Closure(MutatingScope, TypeSpecifierContext): SpecifiedTypes + */ + private function createSpecifyTypesCallback(Assign $expr): Closure + { + return function (MutatingScope $s, TypeSpecifierContext $context) use ($expr): SpecifiedTypes { + if ($context->null()) { + return (new SpecifiedTypes([], []))->setRootExpr($expr); + } + + $specifiedTypes = $this->defaultNarrowingHelper->specifyDefaultTypes($expr->var, $context)->setRootExpr($expr); + + // infer $arr[$key] after $key = array_key_first/last($arr) + if ( + $expr->expr instanceof FuncCall + && $expr->expr->name instanceof Name + && !$expr->expr->isFirstClassCallable() + && in_array($expr->expr->name->toLowerString(), ['array_key_first', 'array_key_last'], true) + && count($expr->expr->getArgs()) >= 1 + ) { + $arrayArg = $expr->expr->getArgs()[0]->value; + $arrayType = $s->getType($arrayArg); + + if ($arrayType->isArray()->yes()) { + if ($context->true()) { + $specifiedTypes = $specifiedTypes->unionWith( + $this->defaultNarrowingHelper->createSubjectTypes($s, $arrayArg, null, new NonEmptyArrayType(), TypeSpecifierContext::createTrue()), + ); + $isNonEmpty = true; + } else { + $isNonEmpty = $arrayType->isIterableAtLeastOnce()->yes(); + } + + if ($isNonEmpty) { + $dimFetch = new ArrayDimFetch($arrayArg, $expr->var); + $specifiedTypes = $specifiedTypes->unionWith( + $this->defaultNarrowingHelper->createSubjectTypes($s, $dimFetch, null, $arrayType->getIterableValueType(), TypeSpecifierContext::createTrue()), + ); + } elseif ($expr->var instanceof Variable && is_string($expr->var->name)) { + $keyType = $s->getType($expr->expr); + $nonNullKeyType = TypeCombinator::removeNull($keyType); + if (!$nonNullKeyType instanceof NeverType) { + $specifiedTypes = $specifiedTypes->unionWith( + $this->createArrayDimFetchConditionalExpressionHolder($expr->var, $arrayArg, $nonNullKeyType, $arrayType->getIterableValueType()), + ); + } + } + } + } + + // infer $arr[$key] after $key = array_search($needle, $arr) or $key = array_find_key($arr, $callback) + if ( + $expr->expr instanceof FuncCall + && $expr->expr->name instanceof Name + && !$expr->expr->isFirstClassCallable() + && count($expr->expr->getArgs()) >= 2 + ) { + $funcName = $expr->expr->name->toLowerString(); + $arrayArg = null; + $sentinelType = null; + $isStrictArraySearch = false; + + if ($funcName === 'array_search') { + $arrayArg = $expr->expr->getArgs()[1]->value; + $sentinelType = new ConstantBooleanType(false); + $isStrictArraySearch = count($expr->expr->getArgs()) >= 3 && $s->getType($expr->expr->getArgs()[2]->value)->isTrue()->yes(); + } elseif ($funcName === 'array_find_key') { + $arrayArg = $expr->expr->getArgs()[0]->value; + $sentinelType = new NullType(); + } + + if ($arrayArg !== null) { + $arrayType = $s->getType($arrayArg); + + if ($arrayType->isArray()->yes()) { + if ($context->true()) { + $specifiedTypes = $specifiedTypes->unionWith( + $this->defaultNarrowingHelper->createSubjectTypes($s, $arrayArg, null, new NonEmptyArrayType(), TypeSpecifierContext::createTrue()), + ); + + $dimFetch = new ArrayDimFetch($arrayArg, $expr->var); + + if ($isStrictArraySearch) { + $needleType = $s->getType($expr->expr->getArgs()[0]->value); + $dimFetchType = TypeCombinator::intersect($needleType, $arrayType->getIterableValueType()); + } else { + $dimFetchType = $arrayType->getIterableValueType(); + } + + $specifiedTypes = $specifiedTypes->unionWith( + $this->defaultNarrowingHelper->createSubjectTypes($s, $dimFetch, null, $dimFetchType, TypeSpecifierContext::createTrue()), + ); + } elseif ($expr->var instanceof Variable && is_string($expr->var->name)) { + $keyType = $s->getType($expr->expr); + $narrowedKeyType = TypeCombinator::remove($keyType, $sentinelType); + if (!$narrowedKeyType instanceof NeverType) { + if ($isStrictArraySearch) { + $needleType = $s->getType($expr->expr->getArgs()[0]->value); + $dimFetchType = TypeCombinator::intersect($needleType, $arrayType->getIterableValueType()); + } else { + $dimFetchType = $arrayType->getIterableValueType(); + } + $specifiedTypes = $specifiedTypes->unionWith( + $this->createArrayDimFetchConditionalExpressionHolder($expr->var, $arrayArg, $narrowedKeyType, $dimFetchType), + ); + } + } + } + } + } + + return $specifiedTypes; + }; + } + /** * @param callable(Node $node, Scope $scope): void $nodeCallback * @param Closure(MutatingScope $scope): ExpressionResult $processExprCallback diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index fa8cc9fcbc8..70541ebae02 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -9,6 +9,7 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; @@ -36,6 +37,7 @@ final class CoalesceHandler implements TypeResolvingExprHandler public function __construct( private NonNullabilityHelper $nonNullabilityHelper, private ExpressionResultFactory $expressionResultFactory, + private DefaultNarrowingHelper $defaultNarrowingHelper, ) { } @@ -140,6 +142,22 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex isAlwaysTerminating: $condResult->isAlwaysTerminating(), throwPoints: array_merge($condResult->getThrowPoints(), $rightResult->getThrowPoints()), impurePoints: array_merge($condResult->getImpurePoints(), $rightResult->getImpurePoints()), + // a type constraint on the coalesce constrains its left side when + // the type rules the right side in or out - what + // TypeSpecifier::create() recovered by unwrapping the coalesce + createTypesCallback: function (MutatingScope $s, Type $type, TypeSpecifierContext $context) use ($expr, $condResult, $rightResult): SpecifiedTypes { + if (!$context->null()) { + $rightType = $rightResult->getTypeForScope($s); + if ( + ($context->true() && $type->isSuperTypeOf($rightType)->no()) + || ($context->false() && $type->isSuperTypeOf($rightType)->yes()) + ) { + return $this->defaultNarrowingHelper->createSubjectTypes($s, $expr->left, $condResult, $type, $context); + } + } + + return $this->defaultNarrowingHelper->createSubjectTypes($s, $expr, null, $type, $context); + }, ); } diff --git a/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php b/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php new file mode 100644 index 00000000000..a82899110bb --- /dev/null +++ b/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php @@ -0,0 +1,91 @@ +` - they + * emit the plain-chain variant alongside their own key once, and every parent + * simply composes their results. No recursive chain-walking, no type ask. + */ +#[AutowiredService] +final class DefaultNarrowingHelper +{ + + public function __construct(private ExprPrinter $exprPrinter) + { + } + + public function specifyDefaultTypes(Expr $expr, TypeSpecifierContext $context): SpecifiedTypes + { + if ($context->null()) { + return (new SpecifiedTypes([], []))->setRootExpr($expr); + } + + if (!$context->truthy()) { + $removedType = StaticTypeFactory::truthy(); + } elseif (!$context->falsey()) { + $removedType = StaticTypeFactory::falsey(); + } else { + return (new SpecifiedTypes([], []))->setRootExpr($expr); + } + + return (new SpecifiedTypes(sureNotTypes: [ + $this->exprPrinter->printExpr($expr) => [$expr, $removedType], + ]))->setRootExpr($expr); + } + + /** + * A greatly simplified TypeSpecifier::create() for a subject the calling + * handler has already processed: one sure (truthy) or sureNot (falsey) + * entry for the subject node. A coalesce subject narrows its left side + * when the narrowed type rules the right side in or out. No purity gates, + * no nullsafe chain-walking, no assignment fan-out - an entry about an + * assignment narrows the assigned variables in the appliers, and the + * subject's own narrowing composes in through + * ExpressionResult::getSpecifiedTypesForScope() at the call site. + */ + /** + * A greatly simplified TypeSpecifier::create() for a subject the calling + * handler has already processed: the subject's own result says how a type + * constraint on it translates into entries (an assignment fans out to the + * assigned variable, a coalesce delegates to its left side); without a + * createTypesCallback a single sure (truthy) or sureNot (falsey) entry + * for the subject node is emitted. No purity gates, no nullsafe + * chain-walking, no structural unwrapping - the handlers that own those + * nodes compose their children's results inside-out. + */ + public function createSubjectTypes(MutatingScope $s, Expr $subject, ?ExpressionResult $subjectResult, Type $type, TypeSpecifierContext $context): SpecifiedTypes + { + if ($subjectResult !== null) { + $createdTypes = $subjectResult->getCreatedTypesForScope($s, $type, $context); + if ($createdTypes !== null) { + return $createdTypes; + } + } + + $exprString = $this->exprPrinter->printExpr($subject); + if ($context->true()) { + return new SpecifiedTypes([$exprString => [$subject, $type]], []); + } + if ($context->false()) { + return new SpecifiedTypes(sureNotTypes: [$exprString => [$subject, $type]]); + } + + return new SpecifiedTypes([], []); + } + +} diff --git a/src/Analyser/ExprHandler/InstanceofHandler.php b/src/Analyser/ExprHandler/InstanceofHandler.php index ff8bda6c552..aaaa04fbed1 100644 --- a/src/Analyser/ExprHandler/InstanceofHandler.php +++ b/src/Analyser/ExprHandler/InstanceofHandler.php @@ -11,10 +11,10 @@ use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; +use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\BooleanType; @@ -40,7 +40,7 @@ final class InstanceofHandler implements ExprHandler public function __construct( private ExpressionResultFactory $expressionResultFactory, - private TypeSpecifier $typeSpecifier, + private DefaultNarrowingHelper $defaultNarrowingHelper, ) { } @@ -146,7 +146,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } else { $type = new ObjectType($className); } - return $this->typeSpecifier->create($exprNode, $type, $context, $s)->setRootExpr($expr); + return $this->defaultNarrowingHelper->createSubjectTypes($s, $exprNode, $exprResult, $type, $context)->setRootExpr($expr); } $classNameType = $classResult !== null @@ -162,16 +162,16 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $type, new ObjectWithoutClassType(), ); - return $this->typeSpecifier->create($exprNode, $type, $context, $s)->setRootExpr($expr); + return $this->defaultNarrowingHelper->createSubjectTypes($s, $exprNode, $exprResult, $type, $context)->setRootExpr($expr); } elseif ($context->false() && !$uncertainty) { $exprType = $exprResult->getTypeForScope($s); if (!$type->isSuperTypeOf($exprType)->yes()) { - return $this->typeSpecifier->create($exprNode, $type, $context, $s)->setRootExpr($expr); + return $this->defaultNarrowingHelper->createSubjectTypes($s, $exprNode, $exprResult, $type, $context)->setRootExpr($expr); } } } if ($context->true()) { - return $this->typeSpecifier->create($exprNode, new ObjectWithoutClassType(), $context, $s)->setRootExpr($exprNode); + return $this->defaultNarrowingHelper->createSubjectTypes($s, $exprNode, $exprResult, new ObjectWithoutClassType(), $context)->setRootExpr($exprNode); } return (new SpecifiedTypes([], []))->setRootExpr($expr); diff --git a/src/Analyser/ExprHandler/VariableHandler.php b/src/Analyser/ExprHandler/VariableHandler.php index 1fc9923355f..03692785a53 100644 --- a/src/Analyser/ExprHandler/VariableHandler.php +++ b/src/Analyser/ExprHandler/VariableHandler.php @@ -13,12 +13,12 @@ use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; +use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\ErrorType; @@ -38,7 +38,7 @@ final class VariableHandler implements ExprHandler public function __construct( private ExpressionResultFactory $expressionResultFactory, - private TypeSpecifier $typeSpecifier, + private DefaultNarrowingHelper $defaultNarrowingHelper, ) { } @@ -123,7 +123,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex throwPoints: $throwPoints, impurePoints: $impurePoints, typeCallback: self::createTypeCallback($expr, $nameResult), - specifyTypesCallback: fn (MutatingScope $s, TypeSpecifierContext $context): SpecifiedTypes => $this->typeSpecifier->specifyDefaultTypes($s, $expr, $context), + specifyTypesCallback: fn (MutatingScope $s, TypeSpecifierContext $context): SpecifiedTypes => $this->defaultNarrowingHelper->specifyDefaultTypes($expr, $context), ); } diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 411e1edeefc..d1e1949d8bc 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -18,6 +18,9 @@ final class ExpressionResult /** @var (callable(MutatingScope, TypeSpecifierContext): SpecifiedTypes)|null */ private $specifyTypesCallback; + /** @var (callable(MutatingScope, Type, TypeSpecifierContext): SpecifiedTypes)|null */ + private $createTypesCallback; + /** @var (callable(): MutatingScope)|null */ private $truthyScopeCallback; @@ -37,6 +40,7 @@ final class ExpressionResult * @param ImpurePoint[] $impurePoints * @param (callable(MutatingScope, Expr): Type)|null $typeCallback * @param (callable(MutatingScope, TypeSpecifierContext): SpecifiedTypes)|null $specifyTypesCallback + * @param (callable(MutatingScope, Type, TypeSpecifierContext): SpecifiedTypes)|null $createTypesCallback * @param (callable(): MutatingScope)|null $truthyScopeCallback * @param (callable(): MutatingScope)|null $falseyScopeCallback */ @@ -53,12 +57,14 @@ public function __construct( ?callable $falseyScopeCallback = null, ?callable $typeCallback = null, ?callable $specifyTypesCallback = null, + ?callable $createTypesCallback = null, ) { $this->truthyScopeCallback = $truthyScopeCallback; $this->falseyScopeCallback = $falseyScopeCallback; $this->typeCallback = $typeCallback; $this->specifyTypesCallback = $specifyTypesCallback; + $this->createTypesCallback = $createTypesCallback; } public function getScope(): MutatingScope @@ -189,6 +195,25 @@ public function getSpecifiedTypesForScope(MutatingScope $scope, TypeSpecifierCon return ($this->specifyTypesCallback)($scope, $context); } + /** + * How a type constraint on this expression translates into narrowing + * entries - the inside-out counterpart of TypeSpecifier::create(). The + * handler that produced this result knows the structure: an assignment + * fans out to the assigned variable and the assigned expression + * (recursing through the assigned expression's own result), a coalesce + * delegates to its left side when the type rules the right side in or + * out. Returns null when the handler wired no createTypesCallback - the + * caller emits a single entry for the expression itself. + */ + public function getCreatedTypesForScope(MutatingScope $scope, Type $type, TypeSpecifierContext $context): ?SpecifiedTypes + { + if ($this->createTypesCallback === null) { + return null; + } + + return ($this->createTypesCallback)($scope, $type, $context); + } + /** * Re-evaluates the expression type on a different scope (e.g. a narrowed one). * Unlike getType(), the result is not cached. diff --git a/src/Analyser/ExpressionResultFactory.php b/src/Analyser/ExpressionResultFactory.php index 43926896f5f..83172cd7eb6 100644 --- a/src/Analyser/ExpressionResultFactory.php +++ b/src/Analyser/ExpressionResultFactory.php @@ -15,6 +15,7 @@ interface ExpressionResultFactory * @param (callable(): MutatingScope)|null $falseyScopeCallback * @param (callable(MutatingScope, Expr): Type)|null $typeCallback * @param (callable(MutatingScope, TypeSpecifierContext): SpecifiedTypes)|null $specifyTypesCallback + * @param (callable(MutatingScope, Type, TypeSpecifierContext): SpecifiedTypes)|null $createTypesCallback */ public function create( MutatingScope $scope, @@ -28,6 +29,7 @@ public function create( ?callable $falseyScopeCallback = null, ?callable $typeCallback = null, ?callable $specifyTypesCallback = null, + ?callable $createTypesCallback = null, ): ExpressionResult; } From fb9b42736cbc632c4c2fa12c17f58248d761b2d0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 13 Jun 2026 00:02:06 +0200 Subject: [PATCH 19/22] Coalesce, Ternary, BooleanAnd, BooleanOr stop implementing TypeResolvingExprHandler The composite handlers wire typeCallback and specifyTypesCallback composed from their operands' results. This deletes the founding pathology of the rewrite: BooleanAndHandler::resolveType's re-walk of the left operand on a throwaway storage, BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH and both flattened-chain code paths - deep chains compose through nested results. Child narrowing flows through DefaultNarrowingHelper::getChildSpecifiedTypes(): the child result's specifyTypesCallback first, bridged through the old-world dispatcher for unmigrated children (the dispatcher answers converted handlers from stored results, so the bridge terminates; it dies in 3.0). The ternary still rewrites itself as (cond && if) || (!cond && else) - the synthetic takes the on-demand path where its real subnodes answer from stored results. Lessons the conversion forced out of the engine: - A handler must never ask the scope about its own node mid-processing - no stored result exists yet, so the ask takes the on-demand path and recurses infinitely (CoalesceHandler's filterByFalseyValue($expr) for the right-side scope hung the suite). The equivalent narrowing is built directly from the left result instead. - Composite typeCallbacks evaluate later operands on their captured processing scopes. Re-filtering the asking scope loses the left side's side effects (by-ref writes, inline assignments); the child result's own point breaks synthetic compositions (min()'s $a < $b ? $a : $b reuses stored results of the real arg nodes, predating the synthetic's branch narrowing). The captured scope has both; native asks flavor it with doNotTreatPhpDocTypesAsCertain(). - ExpressionResult::getType()/getNativeType()/getTypeForScope() consult tracked expression holders before the typeCallback, mirroring the early return in MutatingScope::resolveType() - that is how the nullsafe handlers' ensured non-nullability of ($x ?? null) reaches type asks. Co-Authored-By: Claude Fable 5 --- .../ExprHandler/BooleanAndHandler.php | 265 ++++++------------ src/Analyser/ExprHandler/BooleanOrHandler.php | 251 +++++------------ src/Analyser/ExprHandler/CoalesceHandler.php | 135 +++++---- .../Helper/DefaultNarrowingHelper.php | 27 +- src/Analyser/ExprHandler/TernaryHandler.php | 132 ++++----- src/Analyser/ExpressionResult.php | 21 +- 6 files changed, 337 insertions(+), 494 deletions(-) diff --git a/src/Analyser/ExprHandler/BooleanAndHandler.php b/src/Analyser/ExprHandler/BooleanAndHandler.php index 779c7c014f1..9196a2ef015 100644 --- a/src/Analyser/ExprHandler/BooleanAndHandler.php +++ b/src/Analyser/ExprHandler/BooleanAndHandler.php @@ -4,48 +4,39 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp\BooleanAnd; -use PhpParser\Node\Expr\BinaryOp\BooleanOr; use PhpParser\Node\Expr\BinaryOp\LogicalAnd; -use PhpParser\Node\Expr\BinaryOp\LogicalOr; use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ConditionalExpressionHolderHelper; +use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; -use PHPStan\Analyser\NoopNodeCallback; -use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeResolvingExprHandler; -use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\BooleanAndNode; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; use function array_merge; -use function array_reverse; use function is_string; /** - * @implements TypeResolvingExprHandler + * @implements ExprHandler */ #[AutowiredService] -final class BooleanAndHandler implements TypeResolvingExprHandler +final class BooleanAndHandler implements ExprHandler { - private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4; - public function __construct( - private NodeScopeResolver $nodeScopeResolver, private ConditionalExpressionHolderHelper $conditionalExpressionHolderHelper, private ExpressionResultFactory $expressionResultFactory, + private DefaultNarrowingHelper $defaultNarrowingHelper, ) { } @@ -55,173 +46,6 @@ public function supports(Expr $expr): bool return $expr instanceof BooleanAnd || $expr instanceof LogicalAnd; } - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - $leftBooleanType = $scope->getType($expr->left)->toBoolean(); - if ($leftBooleanType->isFalse()->yes()) { - return new ConstantBooleanType(false); - } - - if (self::getBooleanExpressionDepth($expr->left) <= self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) { - $leftResult = $this->nodeScopeResolver->processExprNode(new Stmt\Expression($expr->left), $expr->left, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); - $rightBooleanType = $leftResult->getTruthyScope()->getType($expr->right)->toBoolean(); - } else { - $rightBooleanType = $scope->filterByTruthyValue($expr->left)->getType($expr->right)->toBoolean(); - } - - if ($rightBooleanType->isFalse()->yes()) { - return new ConstantBooleanType(false); - } - - if ( - $leftBooleanType->isTrue()->yes() - && $rightBooleanType->isTrue()->yes() - ) { - return new ConstantBooleanType(true); - } - - return new BooleanType(); - } - - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - if (!$scope instanceof MutatingScope) { - throw new ShouldNotHappenException(); - } - - // For deep BooleanAnd chains in truthy context, flatten and - // process all arms at once to avoid O(N²) recursive - // filterByTruthyValue calls. - if ( - $context->true() - && self::getBooleanExpressionDepth($expr) > self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH - ) { - return $this->specifyTypesForFlattenedBooleanAnd($typeSpecifier, $scope, $expr, $context); - } - - $leftTypes = $typeSpecifier->specifyTypesInCondition($scope, $expr->left, $context)->setRootExpr($expr); - $rightScope = $scope->filterByTruthyValue($expr->left); - $rightTypes = $typeSpecifier->specifyTypesInCondition($rightScope, $expr->right, $context)->setRootExpr($expr); - if ($context->true()) { - $types = $leftTypes->unionWith($rightTypes); - } else { - $leftNormalized = $leftTypes->normalize($scope); - $rightNormalized = $rightTypes->normalize($rightScope); - $types = $leftNormalized->intersectWith($rightNormalized); - $types = $this->conditionalExpressionHolderHelper->augmentDisjunctionTypes($scope, $rightScope, $leftNormalized, $rightNormalized, $expr->left, $expr->right, false, $types); - } - if ($context->false()) { - $leftTypesForHolders = $leftTypes; - $rightTypesForHolders = $rightTypes; - // In a mixed truthy-and-false context, re-derive empty holders from the falsey narrowing. - if ($context->truthy()) { - if ($leftTypesForHolders->getSureTypes() === [] && $leftTypesForHolders->getSureNotTypes() === []) { - $leftTypesForHolders = $typeSpecifier->specifyTypesInCondition($scope, $expr->left, TypeSpecifierContext::createFalsey())->setRootExpr($expr); - } - if ($rightTypesForHolders->getSureTypes() === [] && $rightTypesForHolders->getSureNotTypes() === []) { - $rightTypesForHolders = $typeSpecifier->specifyTypesInCondition($rightScope, $expr->right, TypeSpecifierContext::createFalsey())->setRootExpr($expr); - } - } - // For arms still empty (e.g. isset() on an array dim fetch), derive conditions - // from the truthy narrowing instead, swapping sure/sureNot types. - if ($leftTypesForHolders->getSureTypes() === [] && $leftTypesForHolders->getSureNotTypes() === []) { - $truthyLeftTypes = $typeSpecifier->specifyTypesInCondition($scope, $expr->left, TypeSpecifierContext::createTruthy()); - if ($this->allExpressionsTrackable($truthyLeftTypes)) { - $leftTypesForHolders = new SpecifiedTypes($truthyLeftTypes->getSureNotTypes(), $truthyLeftTypes->getSureTypes()); - } - } - if ($rightTypesForHolders->getSureTypes() === [] && $rightTypesForHolders->getSureNotTypes() === []) { - $truthyRightTypes = $typeSpecifier->specifyTypesInCondition($rightScope, $expr->right, TypeSpecifierContext::createTruthy()); - if ($this->allExpressionsTrackable($truthyRightTypes)) { - $rightTypesForHolders = new SpecifiedTypes($truthyRightTypes->getSureNotTypes(), $truthyRightTypes->getSureTypes()); - } - } - $result = new SpecifiedTypes( - $types->getSureTypes(), - $types->getSureNotTypes(), - ); - if ($types->shouldOverwrite()) { - $result = $result->setAlwaysOverwriteTypes(); - } - return $result->setNewConditionalExpressionHolders($this->conditionalExpressionHolderHelper->mergeConditionalHolders([ - $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders, false, true, $rightScope, $expr->right), - $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders, false, true, $scope, $expr->left), - $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders, true, true, $rightScope, $expr->right), - $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders, true, true, $scope, $expr->left), - ]))->setRootExpr($expr); - } - - return $types; - } - - public static function getBooleanExpressionDepth(Expr $expr, int $depth = 0): int - { - while ( - $expr instanceof BooleanOr - || $expr instanceof LogicalOr - || $expr instanceof BooleanAnd - || $expr instanceof LogicalAnd - ) { - return self::getBooleanExpressionDepth($expr->left, $depth + 1); - } - - return $depth; - } - - /** - * Flatten a deep BooleanAnd chain into leaf expressions and process them - * without recursive filterByTruthyValue calls. - * - * @param BooleanAnd|LogicalAnd $expr - */ - private function specifyTypesForFlattenedBooleanAnd( - TypeSpecifier $typeSpecifier, - MutatingScope $scope, - Expr $expr, - TypeSpecifierContext $context, - ): SpecifiedTypes - { - $arms = []; - $current = $expr; - while ($current instanceof BooleanAnd || $current instanceof LogicalAnd) { - $arms[] = $current->right; - $current = $current->left; - } - $arms[] = $current; - $arms = array_reverse($arms); - - // Truthy: all arms are true → union all SpecifiedTypes. - // Collect per-expression types first, then build unions once - // to avoid O(N²) from incremental growth. - /** @var array}> $sureTypesPerExpr */ - $sureTypesPerExpr = []; - /** @var array}> $sureNotTypesPerExpr */ - $sureNotTypesPerExpr = []; - - foreach ($arms as $arm) { - $armTypes = $typeSpecifier->specifyTypesInCondition($scope, $arm, $context); - foreach ($armTypes->getSureTypes() as $exprString => [$exprNode, $type]) { - $sureTypesPerExpr[$exprString][0] = $exprNode; - $sureTypesPerExpr[$exprString][1][] = $type; - } - foreach ($armTypes->getSureNotTypes() as $exprString => [$exprNode, $type]) { - $sureNotTypesPerExpr[$exprString][0] = $exprNode; - $sureNotTypesPerExpr[$exprString][1][] = $type; - } - } - - $sureTypes = []; - foreach ($sureTypesPerExpr as $exprString => [$exprNode, $types]) { - $sureTypes[$exprString] = [$exprNode, TypeCombinator::union(...$types)]; - } - $sureNotTypes = []; - foreach ($sureNotTypesPerExpr as $exprString => [$exprNode, $types]) { - $sureNotTypes[$exprString] = [$exprNode, TypeCombinator::union(...$types)]; - } - - return (new SpecifiedTypes($sureTypes, $sureNotTypes))->setRootExpr($expr); - } - private function allExpressionsTrackable(SpecifiedTypes $types): bool { foreach ($types->getSureTypes() as [$expr]) { @@ -273,6 +97,85 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex impurePoints: array_merge($leftResult->getImpurePoints(), $rightResult->getImpurePoints()), truthyScopeCallback: static fn (): MutatingScope => $rightResult->getScope()->filterByTruthyValue($expr->right), falseyScopeCallback: static fn (): MutatingScope => $leftMergedWithRightScope->filterByFalseyValue($expr), + typeCallback: static function (MutatingScope $s) use ($leftResult, $rightResult, $leftTruthyScope): Type { + $leftBooleanType = $leftResult->getTypeForScope($s)->toBoolean(); + if ($leftBooleanType->isFalse()->yes()) { + return new ConstantBooleanType(false); + } + + // the right side was processed on the left-truthy scope including + // the left's side effects (assignments, by-ref writes) - that + // captured scope is the evaluation point, no re-walk and no + // depth cap needed + $rightBooleanType = $rightResult->getTypeForScope($s->nativeTypesPromoted ? $leftTruthyScope->doNotTreatPhpDocTypesAsCertain() : $leftTruthyScope)->toBoolean(); + if ($rightBooleanType->isFalse()->yes()) { + return new ConstantBooleanType(false); + } + + if ( + $leftBooleanType->isTrue()->yes() + && $rightBooleanType->isTrue()->yes() + ) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + }, + specifyTypesCallback: function (MutatingScope $s, TypeSpecifierContext $context) use ($expr, $leftResult, $rightResult): SpecifiedTypes { + $leftTypes = $this->defaultNarrowingHelper->getChildSpecifiedTypes($s, $expr->left, $leftResult, $context)->setRootExpr($expr); + $rightScope = $s->filterByTruthyValue($expr->left); + $rightTypes = $this->defaultNarrowingHelper->getChildSpecifiedTypes($rightScope, $expr->right, $rightResult, $context)->setRootExpr($expr); + if ($context->true()) { + $types = $leftTypes->unionWith($rightTypes); + } else { + $leftNormalized = $leftTypes->normalize($s); + $rightNormalized = $rightTypes->normalize($rightScope); + $types = $leftNormalized->intersectWith($rightNormalized); + $types = $this->conditionalExpressionHolderHelper->augmentDisjunctionTypes($s, $rightScope, $leftNormalized, $rightNormalized, $expr->left, $expr->right, false, $types); + } + if ($context->false()) { + $leftTypesForHolders = $leftTypes; + $rightTypesForHolders = $rightTypes; + // In a mixed truthy-and-false context, re-derive empty holders from the falsey narrowing. + if ($context->truthy()) { + if ($leftTypesForHolders->getSureTypes() === [] && $leftTypesForHolders->getSureNotTypes() === []) { + $leftTypesForHolders = $this->defaultNarrowingHelper->getChildSpecifiedTypes($s, $expr->left, $leftResult, TypeSpecifierContext::createFalsey())->setRootExpr($expr); + } + if ($rightTypesForHolders->getSureTypes() === [] && $rightTypesForHolders->getSureNotTypes() === []) { + $rightTypesForHolders = $this->defaultNarrowingHelper->getChildSpecifiedTypes($rightScope, $expr->right, $rightResult, TypeSpecifierContext::createFalsey())->setRootExpr($expr); + } + } + // For arms still empty (e.g. isset() on an array dim fetch), derive conditions + // from the truthy narrowing instead, swapping sure/sureNot types. + if ($leftTypesForHolders->getSureTypes() === [] && $leftTypesForHolders->getSureNotTypes() === []) { + $truthyLeftTypes = $this->defaultNarrowingHelper->getChildSpecifiedTypes($s, $expr->left, $leftResult, TypeSpecifierContext::createTruthy()); + if ($this->allExpressionsTrackable($truthyLeftTypes)) { + $leftTypesForHolders = new SpecifiedTypes($truthyLeftTypes->getSureNotTypes(), $truthyLeftTypes->getSureTypes()); + } + } + if ($rightTypesForHolders->getSureTypes() === [] && $rightTypesForHolders->getSureNotTypes() === []) { + $truthyRightTypes = $this->defaultNarrowingHelper->getChildSpecifiedTypes($rightScope, $expr->right, $rightResult, TypeSpecifierContext::createTruthy()); + if ($this->allExpressionsTrackable($truthyRightTypes)) { + $rightTypesForHolders = new SpecifiedTypes($truthyRightTypes->getSureNotTypes(), $truthyRightTypes->getSureTypes()); + } + } + $result = new SpecifiedTypes( + $types->getSureTypes(), + $types->getSureNotTypes(), + ); + if ($types->shouldOverwrite()) { + $result = $result->setAlwaysOverwriteTypes(); + } + return $result->setNewConditionalExpressionHolders($this->conditionalExpressionHolderHelper->mergeConditionalHolders([ + $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($s, $leftTypesForHolders, $rightTypesForHolders, false, true, $rightScope, $expr->right), + $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($s, $rightTypesForHolders, $leftTypesForHolders, false, true, $s, $expr->left), + $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($s, $leftTypesForHolders, $rightTypesForHolders, true, true, $rightScope, $expr->right), + $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($s, $rightTypesForHolders, $leftTypesForHolders, true, true, $s, $expr->left), + ]))->setRootExpr($expr); + } + + return $types; + }, ); } diff --git a/src/Analyser/ExprHandler/BooleanOrHandler.php b/src/Analyser/ExprHandler/BooleanOrHandler.php index c0ffac43f45..6e9857ce6ba 100644 --- a/src/Analyser/ExprHandler/BooleanOrHandler.php +++ b/src/Analyser/ExprHandler/BooleanOrHandler.php @@ -10,18 +10,15 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\ConditionalExpressionHolderHelper; +use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; -use PHPStan\Analyser\NoopNodeCallback; -use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeResolvingExprHandler; -use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\BooleanOrNode; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\NeverType; @@ -29,22 +26,18 @@ use PHPStan\Type\TypeCombinator; use function array_key_first; use function array_merge; -use function array_reverse; -use function count; /** - * @implements TypeResolvingExprHandler + * @implements ExprHandler */ #[AutowiredService] -final class BooleanOrHandler implements TypeResolvingExprHandler +final class BooleanOrHandler implements ExprHandler { - private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4; - public function __construct( - private NodeScopeResolver $nodeScopeResolver, private ConditionalExpressionHolderHelper $conditionalExpressionHolderHelper, private ExpressionResultFactory $expressionResultFactory, + private DefaultNarrowingHelper $defaultNarrowingHelper, ) { } @@ -54,168 +47,6 @@ public function supports(Expr $expr): bool return $expr instanceof BooleanOr || $expr instanceof LogicalOr; } - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - $leftBooleanType = $scope->getType($expr->left)->toBoolean(); - if ($leftBooleanType->isTrue()->yes()) { - return new ConstantBooleanType(true); - } - - if (BooleanAndHandler::getBooleanExpressionDepth($expr->left) <= self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) { - $leftResult = $this->nodeScopeResolver->processExprNode(new Stmt\Expression($expr->left), $expr->left, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); - $rightBooleanType = $leftResult->getFalseyScope()->getType($expr->right)->toBoolean(); - } else { - $rightBooleanType = $scope->filterByFalseyValue($expr->left)->getType($expr->right)->toBoolean(); - } - - if ($rightBooleanType->isTrue()->yes()) { - return new ConstantBooleanType(true); - } - - if ( - $leftBooleanType->isFalse()->yes() - && $rightBooleanType->isFalse()->yes() - ) { - return new ConstantBooleanType(false); - } - - return new BooleanType(); - } - - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - if (!$scope instanceof MutatingScope) { - throw new ShouldNotHappenException(); - } - - // For deep BooleanOr chains, flatten and process all arms at once - // to avoid O(n^2) recursive filterByFalseyValue calls - if (BooleanAndHandler::getBooleanExpressionDepth($expr) > self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) { - return $this->specifyTypesForFlattenedBooleanOr($typeSpecifier, $scope, $expr, $context); - } - - $leftTypes = $typeSpecifier->specifyTypesInCondition($scope, $expr->left, $context)->setRootExpr($expr); - $rightScope = $scope->filterByFalseyValue($expr->left); - $rightTypes = $typeSpecifier->specifyTypesInCondition($rightScope, $expr->right, $context)->setRootExpr($expr); - - if ($context->true()) { - if ( - $scope->getType($expr->left)->toBoolean()->isFalse()->yes() - ) { - $types = $rightTypes->normalize($rightScope); - } elseif ( - $scope->getType($expr->left)->toBoolean()->isTrue()->yes() - || $scope->getType($expr->right)->toBoolean()->isFalse()->yes() - ) { - $types = $leftTypes->normalize($scope); - } else { - $leftNormalized = $leftTypes->normalize($scope); - $rightNormalized = $rightTypes->normalize($rightScope); - $types = $leftNormalized->intersectWith($rightNormalized); - $types = $this->augmentBooleanOrTruthyWithConditionalHolders($typeSpecifier, $scope, $rightScope, $expr, $types); - $types = $this->conditionalExpressionHolderHelper->augmentDisjunctionTypes($scope, $rightScope, $leftNormalized, $rightNormalized, $expr->left, $expr->right, true, $types); - } - } else { - $types = $leftTypes->unionWith($rightTypes); - } - - if ($context->true()) { - $result = new SpecifiedTypes( - $types->getSureTypes(), - $types->getSureNotTypes(), - ); - if ($types->shouldOverwrite()) { - $result = $result->setAlwaysOverwriteTypes(); - } - return $result->setNewConditionalExpressionHolders($this->conditionalExpressionHolderHelper->mergeConditionalHolders([ - $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($scope, $leftTypes, $rightTypes, false, false, $rightScope, $expr->right), - $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($scope, $rightTypes, $leftTypes, false, false, $scope, $expr->left), - $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($scope, $leftTypes, $rightTypes, true, false, $rightScope, $expr->right), - $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($scope, $rightTypes, $leftTypes, true, false, $scope, $expr->left), - ]))->setRootExpr($expr); - } - - return $types; - } - - /** - * Flatten a deep BooleanOr chain into leaf expressions and process them - * without recursive filterByFalseyValue calls. This reduces O(n^2) to O(n) - * for chains with many arms (e.g., 80+ === comparisons in ||). - */ - private function specifyTypesForFlattenedBooleanOr( - TypeSpecifier $typeSpecifier, - MutatingScope $scope, - BooleanOr|LogicalOr $expr, - TypeSpecifierContext $context, - ): SpecifiedTypes - { - // Collect all leaf expressions from the chain - $arms = []; - $current = $expr; - while ($current instanceof BooleanOr || $current instanceof LogicalOr) { - $arms[] = $current->right; - $current = $current->left; - } - $arms[] = $current; // leftmost leaf - $arms = array_reverse($arms); - - if ($context->false() || $context->falsey()) { - // Falsey: all arms are false → union all SpecifiedTypes. - // Collect per-expression types first, then build unions once - // to avoid O(N²) from incremental TypeCombinator::union() growth. - /** @var array}> $sureTypesPerExpr */ - $sureTypesPerExpr = []; - /** @var array}> $sureNotTypesPerExpr */ - $sureNotTypesPerExpr = []; - - foreach ($arms as $arm) { - $armTypes = $typeSpecifier->specifyTypesInCondition($scope, $arm, $context); - foreach ($armTypes->getSureTypes() as $exprString => [$exprNode, $type]) { - $sureTypesPerExpr[$exprString][0] = $exprNode; - $sureTypesPerExpr[$exprString][1][] = $type; - } - foreach ($armTypes->getSureNotTypes() as $exprString => [$exprNode, $type]) { - $sureNotTypesPerExpr[$exprString][0] = $exprNode; - $sureNotTypesPerExpr[$exprString][1][] = $type; - } - } - - $sureTypes = []; - foreach ($sureTypesPerExpr as $exprString => [$exprNode, $types]) { - $sureTypes[$exprString] = [$exprNode, TypeCombinator::intersect(...$types)]; - } - $sureNotTypes = []; - foreach ($sureNotTypesPerExpr as $exprString => [$exprNode, $types]) { - $sureNotTypes[$exprString] = [$exprNode, TypeCombinator::union(...$types)]; - } - - return (new SpecifiedTypes($sureTypes, $sureNotTypes))->setRootExpr($expr); - } - - // Truthy: at least one arm is true → intersect all normalized SpecifiedTypes - $armSpecifiedTypes = []; - foreach ($arms as $arm) { - $armTypes = $typeSpecifier->specifyTypesInCondition($scope, $arm, $context); - $armSpecifiedTypes[] = $armTypes->normalize($scope); - } - - $types = $armSpecifiedTypes[0]; - for ($i = 1; $i < count($armSpecifiedTypes); $i++) { - $types = $types->intersectWith($armSpecifiedTypes[$i]); - } - - $result = new SpecifiedTypes( - $types->getSureTypes(), - $types->getSureNotTypes(), - ); - if ($types->shouldOverwrite()) { - $result = $result->setAlwaysOverwriteTypes(); - } - - return $result->setRootExpr($expr); - } - /** * For `if ($a || $b)` truthy, expressions narrowed by stored conditional * holders (e.g. `$a = $obj instanceof ClassA;` records "when `$a` is @@ -234,7 +65,7 @@ private function specifyTypesForFlattenedBooleanOr( * skipped: in the OR-truthy scope the arm that didn't narrow could still be * the truthy one, so the sound result is the original (unnarrowed) type. */ - private function augmentBooleanOrTruthyWithConditionalHolders(TypeSpecifier $typeSpecifier, MutatingScope $scope, MutatingScope $rightScope, BooleanOr|LogicalOr $expr, SpecifiedTypes $types): SpecifiedTypes + private function augmentBooleanOrTruthyWithConditionalHolders(MutatingScope $scope, MutatingScope $rightScope, BooleanOr|LogicalOr $expr, SpecifiedTypes $types): SpecifiedTypes { $leftTruthyScope = $scope->filterByTruthyValue($expr->left); $rightTruthyScope = $rightScope->filterByTruthyValue($expr->right); @@ -283,7 +114,7 @@ private function augmentBooleanOrTruthyWithConditionalHolders(TypeSpecifier $typ } $types = $types->unionWith( - $typeSpecifier->create($targetExpr, $unionType, TypeSpecifierContext::createTrue(), $scope), + $this->defaultNarrowingHelper->createSubjectTypes($scope, $targetExpr, null, $unionType, TypeSpecifierContext::createTrue()), ); } } @@ -315,6 +146,74 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex impurePoints: array_merge($leftResult->getImpurePoints(), $rightResult->getImpurePoints()), truthyScopeCallback: static fn (): MutatingScope => $leftMergedWithRightScope->filterByTruthyValue($expr), falseyScopeCallback: static fn (): MutatingScope => $rightResult->getScope()->filterByFalseyValue($expr->right), + typeCallback: static function (MutatingScope $s) use ($leftResult, $rightResult, $leftFalseyScope): Type { + $leftBooleanType = $leftResult->getTypeForScope($s)->toBoolean(); + if ($leftBooleanType->isTrue()->yes()) { + return new ConstantBooleanType(true); + } + + // the right side was processed on the left-falsey scope including + // the left's side effects (assignments, by-ref writes) - that + // captured scope is the evaluation point, no re-walk and no + // depth cap needed + $rightBooleanType = $rightResult->getTypeForScope($s->nativeTypesPromoted ? $leftFalseyScope->doNotTreatPhpDocTypesAsCertain() : $leftFalseyScope)->toBoolean(); + if ($rightBooleanType->isTrue()->yes()) { + return new ConstantBooleanType(true); + } + + if ( + $leftBooleanType->isFalse()->yes() + && $rightBooleanType->isFalse()->yes() + ) { + return new ConstantBooleanType(false); + } + + return new BooleanType(); + }, + specifyTypesCallback: function (MutatingScope $s, TypeSpecifierContext $context) use ($expr, $leftResult, $rightResult): SpecifiedTypes { + $leftTypes = $this->defaultNarrowingHelper->getChildSpecifiedTypes($s, $expr->left, $leftResult, $context)->setRootExpr($expr); + $rightScope = $s->filterByFalseyValue($expr->left); + $rightTypes = $this->defaultNarrowingHelper->getChildSpecifiedTypes($rightScope, $expr->right, $rightResult, $context)->setRootExpr($expr); + + if ($context->true()) { + if ( + $leftResult->getTypeForScope($s)->toBoolean()->isFalse()->yes() + ) { + $types = $rightTypes->normalize($rightScope); + } elseif ( + $leftResult->getTypeForScope($s)->toBoolean()->isTrue()->yes() + || $rightResult->getTypeForScope($s)->toBoolean()->isFalse()->yes() + ) { + $types = $leftTypes->normalize($s); + } else { + $leftNormalized = $leftTypes->normalize($s); + $rightNormalized = $rightTypes->normalize($rightScope); + $types = $leftNormalized->intersectWith($rightNormalized); + $types = $this->augmentBooleanOrTruthyWithConditionalHolders($s, $rightScope, $expr, $types); + $types = $this->conditionalExpressionHolderHelper->augmentDisjunctionTypes($s, $rightScope, $leftNormalized, $rightNormalized, $expr->left, $expr->right, true, $types); + } + } else { + $types = $leftTypes->unionWith($rightTypes); + } + + if ($context->true()) { + $result = new SpecifiedTypes( + $types->getSureTypes(), + $types->getSureNotTypes(), + ); + if ($types->shouldOverwrite()) { + $result = $result->setAlwaysOverwriteTypes(); + } + return $result->setNewConditionalExpressionHolders($this->conditionalExpressionHolderHelper->mergeConditionalHolders([ + $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($s, $leftTypes, $rightTypes, false, false, $rightScope, $expr->right), + $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($s, $rightTypes, $leftTypes, false, false, $s, $expr->left), + $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($s, $leftTypes, $rightTypes, true, false, $rightScope, $expr->right), + $this->conditionalExpressionHolderHelper->processBooleanConditionalTypes($s, $rightTypes, $leftTypes, true, false, $s, $expr->left), + ]))->setRootExpr($expr); + } + + return $types; + }, ); } diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index 70541ebae02..27c0fc4a889 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -9,17 +9,14 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; -use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeResolvingExprHandler; -use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; @@ -28,10 +25,10 @@ use function array_merge; /** - * @implements TypeResolvingExprHandler + * @implements ExprHandler */ #[AutowiredService] -final class CoalesceHandler implements TypeResolvingExprHandler +final class CoalesceHandler implements ExprHandler { public function __construct( @@ -47,73 +44,22 @@ public function supports(Expr $expr): bool return $expr instanceof Coalesce; } - public function resolveType(MutatingScope $scope, Expr $expr): Type + /** + * A falsey coalesce means its left side was null (when it was surely set) - + * shared by the specifyTypesCallback and by processExpr() for the scope + * the right side evaluates under. + * + * @param Coalesce $expr + */ + private function getFalseySpecifiedTypes(MutatingScope $s, Expr $expr, ExpressionResult $condResult, TypeSpecifierContext $context): SpecifiedTypes { - $issetLeftExpr = new Expr\Isset_([$expr->left]); + $isset = $s->issetCheck($expr->left, static fn () => true); - $result = $scope->issetCheck($expr->left, static function (Type $type): ?bool { - $isNull = $type->isNull(); - if ($isNull->maybe()) { - return null; - } - - return !$isNull->yes(); - }); - - if ($result !== null && $result !== false) { - return TypeCombinator::removeNull($scope->filterByTruthyValue($issetLeftExpr)->getType($expr->left)); - } - - $rightType = $scope->filterByFalseyValue($issetLeftExpr)->getType($expr->right); - - if ($result === null) { - return TypeCombinator::union( - TypeCombinator::removeNull($scope->filterByTruthyValue($issetLeftExpr)->getType($expr->left)), - $rightType, - ); - } - - return $rightType; - } - - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); - } - - if (!$context->true()) { - if (!$scope instanceof MutatingScope) { - throw new ShouldNotHappenException(); - } - - $isset = $scope->issetCheck($expr->left, static fn () => true); - - if ($isset !== true) { - return new SpecifiedTypes(); - } - - return $typeSpecifier->create( - $expr->left, - new NullType(), - $context->negate(), - $scope, - )->setRootExpr($expr); - } - - if ((new ConstantBooleanType(false))->isSuperTypeOf($scope->getType($expr->right)->toBoolean())->yes()) { - return $typeSpecifier->create( - $expr->left, - new NullType(), - TypeSpecifierContext::createFalse(), - $scope, - )->setRootExpr($expr); + if ($isset !== true) { + return new SpecifiedTypes(); } - // The Coalesce condition matched but produced no narrowing; the legacy - // if/elseif chain fell through to its empty-SpecifiedTypes tail here, - // not to the truthy/falsey default. - return (new SpecifiedTypes([], []))->setRootExpr($expr); + return $this->defaultNarrowingHelper->createSubjectTypes($s, $expr->left, $condResult, new NullType(), $context->negate())->setRootExpr($expr); } public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult @@ -125,7 +71,9 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $this->nonNullabilityHelper->revertNonNullability($condResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); $scope = $nodeScopeResolver->lookForUnsetAllowedUndefinedExpressions($scope, $expr->left); - $rightScope = $scope->filterByFalseyValue($expr); + // the falsey narrowing of this very node - asking the scope about it + // mid-processing would take the on-demand path and recurse + $rightScope = $scope->applySpecifiedTypes($this->getFalseySpecifiedTypes($scope, $expr, $condResult, TypeSpecifierContext::createFalsey())); $rightResult = $nodeScopeResolver->processExprNode($stmt, $expr->right, $rightScope, $storage, $nodeCallback, $context->enterDeep()); $rightExprType = $scope->getType($expr->right); if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { @@ -142,6 +90,53 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex isAlwaysTerminating: $condResult->isAlwaysTerminating(), throwPoints: array_merge($condResult->getThrowPoints(), $rightResult->getThrowPoints()), impurePoints: array_merge($condResult->getImpurePoints(), $rightResult->getImpurePoints()), + typeCallback: static function (MutatingScope $s) use ($expr, $condResult, $rightResult, $rightScope): Type { + $issetLeftExpr = new Expr\Isset_([$expr->left]); + + $result = $s->issetCheck($expr->left, static function (Type $type): ?bool { + $isNull = $type->isNull(); + if ($isNull->maybe()) { + return null; + } + + return !$isNull->yes(); + }); + + if ($result !== null && $result !== false) { + return TypeCombinator::removeNull($condResult->getTypeForScope($s->filterByTruthyValue($issetLeftExpr))); + } + + // the right side was processed on the left-is-null scope - that + // captured scope is the evaluation point + $rightType = $rightResult->getTypeForScope($s->nativeTypesPromoted ? $rightScope->doNotTreatPhpDocTypesAsCertain() : $rightScope); + + if ($result === null) { + return TypeCombinator::union( + TypeCombinator::removeNull($condResult->getTypeForScope($s->filterByTruthyValue($issetLeftExpr))), + $rightType, + ); + } + + return $rightType; + }, + specifyTypesCallback: function (MutatingScope $s, TypeSpecifierContext $context) use ($expr, $condResult, $rightResult): SpecifiedTypes { + if ($context->null()) { + return $this->defaultNarrowingHelper->specifyDefaultTypes($expr, $context); + } + + if (!$context->true()) { + return $this->getFalseySpecifiedTypes($s, $expr, $condResult, $context); + } + + if ((new ConstantBooleanType(false))->isSuperTypeOf($rightResult->getTypeForScope($s)->toBoolean())->yes()) { + return $this->defaultNarrowingHelper->createSubjectTypes($s, $expr->left, $condResult, new NullType(), TypeSpecifierContext::createFalse())->setRootExpr($expr); + } + + // The Coalesce condition matched but produced no narrowing; the legacy + // if/elseif chain fell through to its empty-SpecifiedTypes tail here, + // not to the truthy/falsey default. + return (new SpecifiedTypes([], []))->setRootExpr($expr); + }, // a type constraint on the coalesce constrains its left side when // the type rules the right side in or out - what // TypeSpecifier::create() recovered by unwrapping the coalesce diff --git a/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php b/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php index a82899110bb..84395b4b3d3 100644 --- a/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php +++ b/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Printer\ExprPrinter; @@ -25,10 +26,34 @@ final class DefaultNarrowingHelper { - public function __construct(private ExprPrinter $exprPrinter) + public function __construct( + private ExprPrinter $exprPrinter, + private TypeSpecifier $typeSpecifier, + ) { } + /** + * The narrowing of an already-processed child expression in the given + * boolean context: answered by the child result's specifyTypesCallback. + * Until the child's handler migrates its narrowing - or when the child + * is a synthetic node with no result - this bridges through the + * old-world dispatcher, which answers converted handlers from stored + * results, so the bridge terminates. The bridge dies in 3.0 together + * with TypeSpecifier::specifyTypesInCondition(). + */ + public function getChildSpecifiedTypes(MutatingScope $s, Expr $childExpr, ?ExpressionResult $childResult, TypeSpecifierContext $context): SpecifiedTypes + { + if ($childResult !== null) { + $types = $childResult->getSpecifiedTypesForScope($s, $context); + if ($types !== null) { + return $types; + } + } + + return $this->typeSpecifier->specifyTypesInCondition($s, $childExpr, $context); + } + public function specifyDefaultTypes(Expr $expr, TypeSpecifierContext $context): SpecifiedTypes { if ($context->null()) { diff --git a/src/Analyser/ExprHandler/TernaryHandler.php b/src/Analyser/ExprHandler/TernaryHandler.php index bccabd76e0b..79ece5c6c51 100644 --- a/src/Analyser/ExprHandler/TernaryHandler.php +++ b/src/Analyser/ExprHandler/TernaryHandler.php @@ -11,13 +11,11 @@ use PHPStan\Analyser\ExpressionResult; use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; +use PHPStan\Analyser\ExprHandler; +use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; -use PHPStan\Analyser\NoopNodeCallback; -use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeResolvingExprHandler; -use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\NeverType; @@ -26,15 +24,15 @@ use function array_merge; /** - * @implements TypeResolvingExprHandler + * @implements ExprHandler */ #[AutowiredService] -final class TernaryHandler implements TypeResolvingExprHandler +final class TernaryHandler implements ExprHandler { public function __construct( - private NodeScopeResolver $nodeScopeResolver, private ExpressionResultFactory $expressionResultFactory, + private DefaultNarrowingHelper $defaultNarrowingHelper, ) { } @@ -44,62 +42,6 @@ public function supports(Expr $expr): bool return $expr instanceof Ternary; } - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - $condResult = $this->nodeScopeResolver->processExprNode(new Stmt\Expression($expr->cond), $expr->cond, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); - if ($expr->if === null) { - $conditionType = $scope->getType($expr->cond); - $booleanConditionType = $conditionType->toBoolean(); - if ($booleanConditionType->isTrue()->yes()) { - return $condResult->getTruthyScope()->getType($expr->cond); - } - - if ($booleanConditionType->isFalse()->yes()) { - return $condResult->getFalseyScope()->getType($expr->else); - } - - return TypeCombinator::union( - TypeCombinator::removeFalsey($condResult->getTruthyScope()->getType($expr->cond)), - $condResult->getFalseyScope()->getType($expr->else), - ); - } - - $booleanConditionType = $scope->getType($expr->cond)->toBoolean(); - if ($booleanConditionType->isTrue()->yes()) { - return $condResult->getTruthyScope()->getType($expr->if); - } - - if ($booleanConditionType->isFalse()->yes()) { - return $condResult->getFalseyScope()->getType($expr->else); - } - - return TypeCombinator::union( - $condResult->getTruthyScope()->getType($expr->if), - $condResult->getFalseyScope()->getType($expr->else), - ); - } - - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - if ($expr->cond instanceof Ternary || $context->null()) { - return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); - } - - if ($expr->if !== null) { - $conditionExpr = new BooleanOr( - new BooleanAnd($expr->cond, $expr->if), - new BooleanAnd(new Expr\BooleanNot($expr->cond), $expr->else), - ); - } else { - $conditionExpr = new BooleanOr( - $expr->cond, - new BooleanAnd(new Expr\BooleanNot($expr->cond), $expr->else), - ); - } - - return $typeSpecifier->specifyTypesInCondition($scope, $conditionExpr, $context)->setRootExpr($expr); - } - public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { $ternaryCondResult = $nodeScopeResolver->processExprNode($stmt, $expr->cond, $scope, $storage, $nodeCallback, $context->enterDeep()); @@ -108,7 +50,10 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $ifTrueScope = $ternaryCondResult->getTruthyScope(); $ifFalseScope = $ternaryCondResult->getFalseyScope(); $ifTrueType = null; + $ifResult = null; + $ifProcessingScope = $ifTrueScope; + $elseProcessingScope = $ifFalseScope; if ($expr->if === null) { $elseResult = $nodeScopeResolver->processExprNode($stmt, $expr->else, $ifFalseScope, $storage, $nodeCallback, $context); $throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints()); @@ -154,6 +99,67 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex isAlwaysTerminating: $ternaryCondResult->isAlwaysTerminating(), throwPoints: $throwPoints, impurePoints: $impurePoints, + // the branches were processed on the cond-truthy/cond-falsey scopes + // including the condition's side effects - those captured scopes + // are the evaluation points, no re-walk needed + typeCallback: static function (MutatingScope $s) use ($expr, $ternaryCondResult, $ifResult, $elseResult, $ifProcessingScope, $elseProcessingScope): Type { + if ($s->nativeTypesPromoted) { + $ifProcessingScope = $ifProcessingScope->doNotTreatPhpDocTypesAsCertain(); + $elseProcessingScope = $elseProcessingScope->doNotTreatPhpDocTypesAsCertain(); + } + $booleanConditionType = $ternaryCondResult->getTypeForScope($s)->toBoolean(); + $elseType = $elseResult->getTypeForScope($elseProcessingScope); + if ($expr->if === null || $ifResult === null) { + $condTruthyType = $ternaryCondResult->getTypeForScope($ifProcessingScope); + if ($booleanConditionType->isTrue()->yes()) { + return $condTruthyType; + } + + if ($booleanConditionType->isFalse()->yes()) { + return $elseType; + } + + return TypeCombinator::union( + TypeCombinator::removeFalsey($condTruthyType), + $elseType, + ); + } + + $ifType = $ifResult->getTypeForScope($ifProcessingScope); + if ($booleanConditionType->isTrue()->yes()) { + return $ifType; + } + + if ($booleanConditionType->isFalse()->yes()) { + return $elseType; + } + + return TypeCombinator::union( + $ifType, + $elseType, + ); + }, + specifyTypesCallback: function (MutatingScope $s, TypeSpecifierContext $context) use ($expr): SpecifiedTypes { + if ($expr->cond instanceof Ternary || $context->null()) { + return $this->defaultNarrowingHelper->specifyDefaultTypes($expr, $context); + } + + if ($expr->if !== null) { + $conditionExpr = new BooleanOr( + new BooleanAnd($expr->cond, $expr->if), + new BooleanAnd(new Expr\BooleanNot($expr->cond), $expr->else), + ); + } else { + $conditionExpr = new BooleanOr( + $expr->cond, + new BooleanAnd(new Expr\BooleanNot($expr->cond), $expr->else), + ); + } + + // the synthetic condition takes the on-demand bridge; its real + // subnodes answer from stored results + return $this->defaultNarrowingHelper->getChildSpecifiedTypes($s, $conditionExpr, null, $context)->setRootExpr($expr); + }, ); } diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index d1e1949d8bc..43de235c169 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -156,7 +156,7 @@ public function getType(): Type } } - if ($this->typeCallback !== null) { + if ($this->typeCallback !== null && !$this->hasTrackedExpressionType($this->beforeScope)) { return $this->cachedType = TypeUtils::resolveLateResolvableTypes(($this->typeCallback)($this->beforeScope, $this->expr)); } @@ -169,13 +169,28 @@ public function getNativeType(): Type return $this->cachedNativeType; } - if ($this->typeCallback !== null) { + if ($this->typeCallback !== null && !$this->hasTrackedExpressionType($this->beforeScope->doNotTreatPhpDocTypesAsCertain())) { return $this->cachedNativeType = TypeUtils::resolveLateResolvableTypes(($this->typeCallback)($this->beforeScope->doNotTreatPhpDocTypesAsCertain(), $this->expr)); } return $this->cachedNativeType = $this->beforeScope->getNativeType($this->expr); } + /** + * A narrowed or ensured type tracked for the whole expression (e.g. the + * nullsafe handlers ensure `($x ?? null)` is not null before processing + * the chain) wins over recomputing the type - mirrors the tracked-holder + * early return in MutatingScope::resolveType(). Asking the scope is safe: + * its own early return answers from the holder without dispatching back. + */ + private function hasTrackedExpressionType(MutatingScope $scope): bool + { + return !$this->expr instanceof Expr\Variable + && !$this->expr instanceof Expr\Closure + && !$this->expr instanceof Expr\ArrowFunction + && $scope->hasExpressionType($this->expr)->yes(); + } + public function hasTypeCallback(): bool { return $this->typeCallback !== null; @@ -220,7 +235,7 @@ public function getCreatedTypesForScope(MutatingScope $scope, Type $type, TypeSp */ public function getTypeForScope(MutatingScope $scope): Type { - if ($this->typeCallback !== null) { + if ($this->typeCallback !== null && !$this->hasTrackedExpressionType($scope)) { return TypeUtils::resolveLateResolvableTypes(($this->typeCallback)($scope, $this->expr)); } From 3fd75042f4aee5859cca4d99044670631e43fc42 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 13 Jun 2026 00:11:12 +0200 Subject: [PATCH 20/22] Add regression test for conditional holders narrowing coalesce of properties Closes https://github.com/phpstan/phpstan/issues/10786 Co-Authored-By: Claude Fable 5 --- tests/PHPStan/Analyser/nsrt/bug-10786.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10786.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-10786.php b/tests/PHPStan/Analyser/nsrt/bug-10786.php new file mode 100644 index 00000000000..4dc9333aba6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10786.php @@ -0,0 +1,23 @@ +value) && is_null($b->value)) { + throw new \Exception(); + } + + assertType('int', $a->value ?? $b->value); + + return $a->value ?? $b->value; + } +} From be672de59b64e0d167ddacf2799e9a6c3bf79777 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 13 Jun 2026 00:19:31 +0200 Subject: [PATCH 21/22] Never process expressions on a FiberScope The engine never processes on the rule-facing FiberScope - its type asks suspend, which crashes outside a fiber. One can reach processExprNode through a stored result's memoized truthy/falsey scope (first computed inside a rule fiber) consumed by a composite handler for a child's processing scope: phpstan-phpunit's assertEmpty() extension builds a synthetic BooleanOr, the converted BooleanOrHandler processes it on demand, and the right arm's scope comes from the left arm's stored result. Convert to the mutating flavor at the processExprNode boundary. Co-Authored-By: Claude Fable 5 --- src/Analyser/NodeScopeResolver.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 4cb970ac163..bfcacb80447 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -52,6 +52,7 @@ use PhpParser\NodeVisitorAbstract; use PHPStan\Analyser\ExprHandler\AssignHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; +use PHPStan\Analyser\Fiber\FiberScope; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; use PHPStan\BetterReflection\Reflection\ReflectionEnum; use PHPStan\BetterReflection\Reflector\Reflector; @@ -2803,6 +2804,15 @@ public function processExprNode( ExpressionContext $context, ): ExpressionResult { + if ($scope instanceof FiberScope) { + // the engine never processes on the rule-facing FiberScope - one can + // arrive here through a stored result's memoized truthy/falsey scope + // (first computed inside a rule fiber) consumed by a handler for a + // child's processing scope; its type asks would suspend outside + // a fiber + $scope = $scope->toMutatingScope(); + } + if ($this->returnStoredExpressionResults) { $storedResult = $storage->findExpressionResult($expr); if ($storedResult !== null) { From eb310774fbe795f5029bca61258916f1f0512f51 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 13 Jun 2026 00:36:03 +0200 Subject: [PATCH 22/22] Convert rule-facing FiberScope at the new-world hook boundary Replaces the blanket processExprNode conversion with the root cause: the hooks are the boundary between the rule-facing world and the engine. Rules hold FiberScopes and feed them straight into the old-world dispatcher (ImpossibleCheckTypeHelper passes the rule's scope to specifyTypesInCondition, phpstan-phpunit's assert extension builds a synthetic BooleanOr there), so resolveTypeOfNewWorldHandlerNode() and specifyTypesOfNewWorldHandlerNode() can run with $this being a FiberScope. They now call toMutatingScope() - identity on a plain scope, a state-preserving copy on a FiberScope - before invoking result callbacks and on-demand processing. Without the conversion the engine processes synthetic nodes on the rule-facing scope, whose type asks suspend: wasteful inside a rule fiber, fatal outside one ("Cannot suspend outside of a fiber"). Found by an ExpressionResult creation tripwire after the CI-only crash never reproduced locally. Co-Authored-By: Claude Fable 5 --- src/Analyser/MutatingScope.php | 21 +++++++++++++++------ src/Analyser/NodeScopeResolver.php | 10 ---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 0b9abdf3032..508593ed674 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1012,6 +1012,11 @@ private function resolveType(string $exprString, Expr $node): Type */ private function resolveTypeOfNewWorldHandlerNode(Expr $node): Type { + // the hooks are the boundary between the rule-facing world and the + // engine - a rule's FiberScope must not flow into result callbacks or + // on-demand processing, where its suspending type asks crash outside + // a fiber + $scope = $this->toMutatingScope(); $storage = $this->expressionResultStorageStack->getCurrent(); if ($storage !== null) { $result = $storage->findExpressionResult($node); @@ -1023,18 +1028,18 @@ private function resolveTypeOfNewWorldHandlerNode(Expr $node): Type )); } - return $result->getTypeForScope($this); + return $result->getTypeForScope($scope); } } // a synthetic node, or no analysis in progress $onDemandResult = $this->container->getByType(NodeScopeResolver::class)->processExprOnDemand( $node, - $this, + $scope, $storage !== null ? $storage->duplicate() : new ExpressionResultStorage(), ); - return $onDemandResult->getTypeForScope($this); + return $onDemandResult->getTypeForScope($scope); } /** @@ -1077,22 +1082,26 @@ private function getCurrentTypesOfSpecifiedExpr(Expr $expr): ?array */ public function specifyTypesOfNewWorldHandlerNode(Expr $node, TypeSpecifierContext $context): ?SpecifiedTypes { + // see resolveTypeOfNewWorldHandlerNode() - rules ask the dispatcher + // with their FiberScope (e.g. ImpossibleCheckTypeHelper), the engine + // side of the boundary works with the mutating flavor + $scope = $this->toMutatingScope(); $storage = $this->expressionResultStorageStack->getCurrent(); if ($storage !== null) { $result = $storage->findExpressionResult($node); if ($result !== null) { - return $result->getSpecifiedTypesForScope($this, $context); + return $result->getSpecifiedTypesForScope($scope, $context); } } // a synthetic node, or no analysis in progress $onDemandResult = $this->container->getByType(NodeScopeResolver::class)->processExprOnDemand( $node, - $this, + $scope, $storage !== null ? $storage->duplicate() : new ExpressionResultStorage(), ); - return $onDemandResult->getSpecifiedTypesForScope($this, $context); + return $onDemandResult->getSpecifiedTypesForScope($scope, $context); } /** diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index bfcacb80447..4cb970ac163 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -52,7 +52,6 @@ use PhpParser\NodeVisitorAbstract; use PHPStan\Analyser\ExprHandler\AssignHandler; use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; -use PHPStan\Analyser\Fiber\FiberScope; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; use PHPStan\BetterReflection\Reflection\ReflectionEnum; use PHPStan\BetterReflection\Reflector\Reflector; @@ -2804,15 +2803,6 @@ public function processExprNode( ExpressionContext $context, ): ExpressionResult { - if ($scope instanceof FiberScope) { - // the engine never processes on the rule-facing FiberScope - one can - // arrive here through a stored result's memoized truthy/falsey scope - // (first computed inside a rule fiber) consumed by a handler for a - // child's processing scope; its type asks would suspend outside - // a fiber - $scope = $scope->toMutatingScope(); - } - if ($this->returnStoredExpressionResults) { $storedResult = $storage->findExpressionResult($expr); if ($storedResult !== null) {