From 2042c44bde3168f4a47fe7bc234cd30adfb8c8c9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Mar 2026 10:25:18 +0100 Subject: [PATCH 01/11] Introduce ExpressionResultFactory --- .../ExprHandler/ArrayDimFetchHandler.php | 11 +++++++-- 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 | 9 ++++++- 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 | 9 ++++++- 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 | 9 ++++++- src/Analyser/ExprHandler/EvalHandler.php | 9 ++++++- src/Analyser/ExprHandler/ExitHandler.php | 9 ++++++- src/Analyser/ExprHandler/FuncCallHandler.php | 4 +++- src/Analyser/ExprHandler/IncludeHandler.php | 9 ++++++- .../ExprHandler/InstanceofHandler.php | 9 ++++++- .../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 | 9 ++++++- src/Analyser/ExprHandler/PostDecHandler.php | 9 ++++++- src/Analyser/ExprHandler/PostIncHandler.php | 9 ++++++- src/Analyser/ExprHandler/PreDecHandler.php | 9 ++++++- src/Analyser/ExprHandler/PreIncHandler.php | 9 ++++++- src/Analyser/ExprHandler/PrintHandler.php | 9 ++++++- .../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 | 9 ++++++- .../ExprHandler/UnaryMinusHandler.php | 4 +++- src/Analyser/ExprHandler/UnaryPlusHandler.php | 9 ++++++- src/Analyser/ExprHandler/VariableHandler.php | 9 ++++++- .../Virtual/AlwaysRememberedExprHandler.php | 9 ++++++- .../Virtual/ExistingArrayDimFetchHandler.php | 9 ++++++- .../Virtual/FunctionCallableNodeHandler.php | 9 ++++++- .../Virtual/GetIterableKeyTypeExprHandler.php | 9 ++++++- .../GetIterableValueTypeExprHandler.php | 9 ++++++- .../Virtual/GetOffsetValueTypeExprHandler.php | 9 ++++++- .../InstantiationCallableNodeHandler.php | 9 ++++++- .../Virtual/MethodCallableNodeHandler.php | 9 ++++++- .../Virtual/NativeTypeExprHandler.php | 9 ++++++- .../OriginalPropertyTypeExprHandler.php | 4 +++- .../SetExistingOffsetValueTypeExprHandler.php | 9 ++++++- .../Virtual/SetOffsetValueTypeExprHandler.php | 9 ++++++- .../StaticMethodCallableNodeHandler.php | 9 ++++++- .../ExprHandler/Virtual/TypeExprHandler.php | 9 ++++++- .../Virtual/UnsetOffsetExprHandler.php | 9 ++++++- src/Analyser/ExprHandler/YieldFromHandler.php | 9 ++++++- src/Analyser/ExprHandler/YieldHandler.php | 9 ++++++- 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 ++ 71 files changed, 404 insertions(+), 76 deletions(-) create mode 100644 src/Analyser/ExpressionResultFactory.php diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index baea95ddd5..58bc7720b5 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; @@ -30,6 +31,12 @@ final class ArrayDimFetchHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof ArrayDimFetch; @@ -75,7 +82,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(), @@ -104,7 +111,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 a4d150b0e2..85efc32a3b 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.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; @@ -26,6 +27,7 @@ final class ArrayHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -69,7 +71,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 098101f175..073e54c328 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; @@ -23,6 +24,7 @@ final class ArrowFunctionHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private ClosureTypeResolver $closureTypeResolver, ) { @@ -37,7 +39,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 284ec47619..4e3d843aa6 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -21,6 +21,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; @@ -78,6 +79,7 @@ final class AssignHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private TypeSpecifier $typeSpecifier, private PhpVersion $phpVersion, ) @@ -105,7 +107,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; @@ -145,7 +147,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, ); @@ -159,7 +161,7 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex } } - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), @@ -706,7 +708,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(); @@ -798,7 +800,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 unwrapAssign(Expr $expr): Expr diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index c06057eeef..037fa7f14e 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\InternalThrowPoint; @@ -33,6 +34,7 @@ final class AssignOpHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private AssignHandler $assignHandler, private InitializerExprTypeResolver $initializerExprTypeResolver, ) @@ -55,7 +57,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( @@ -66,7 +68,7 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); if ($expr instanceof Expr\AssignOp\Coalesce) { $nodeScopeResolver->storeBeforeScope($storage, $expr, $originalScope); - return new ExpressionResult( + return $this->expressionResultFactory->create( $exprResult->getScope()->mergeWith($originalScope), $exprResult->hasYield(), $exprResult->isAlwaysTerminating(), @@ -91,7 +93,7 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex $throwPoints[] = InternalThrowPoint::createExplicit($scope, new ObjectType(DivisionByZeroError::class), $expr, false); } - 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 b9d0908f78..c9d584a57e 100644 --- a/src/Analyser/ExprHandler/BinaryOpHandler.php +++ b/src/Analyser/ExprHandler/BinaryOpHandler.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\InternalThrowPoint; @@ -39,6 +40,7 @@ final class BinaryOpHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, private RicherScopeGetTypeHelper $richerScopeGetTypeHelper, private PhpVersion $phpVersion, @@ -70,7 +72,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 715a795404..b648aa3b68 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; @@ -23,6 +24,7 @@ final class BitwiseNotHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -37,7 +39,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 5b672a2db5..a17f866581 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\MutatingScope; @@ -33,6 +34,7 @@ final class BooleanAndHandler implements ExprHandler private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4; public function __construct( + private ExpressionResultFactory $expressionResultFactory, private NodeScopeResolver $nodeScopeResolver, ) { @@ -99,7 +101,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 e718e74e3f..9869036b27 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; @@ -23,6 +24,12 @@ final class BooleanNotHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof BooleanNot; @@ -33,7 +40,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 9c828edb94..9675c4547f 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\MutatingScope; @@ -31,6 +32,7 @@ final class BooleanOrHandler implements ExprHandler private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4; public function __construct( + private ExpressionResultFactory $expressionResultFactory, private NodeScopeResolver $nodeScopeResolver, ) { @@ -83,7 +85,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 212af1adc1..7f2781b732 100644 --- a/src/Analyser/ExprHandler/CastHandler.php +++ b/src/Analyser/ExprHandler/CastHandler.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; @@ -24,6 +25,7 @@ final class CastHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -39,7 +41,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 fff7f07d48..31688f4610 100644 --- a/src/Analyser/ExprHandler/CastStringHandler.php +++ b/src/Analyser/ExprHandler/CastStringHandler.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; @@ -25,6 +26,7 @@ final class CastStringHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -56,7 +58,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 6d9674020e..d7e255e337 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; @@ -26,6 +27,7 @@ final class ClassConstFetchHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -79,7 +81,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 f46bf760e5..14632dbe88 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; @@ -25,6 +26,12 @@ final class CloneHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Clone_; @@ -34,7 +41,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 b706a44dbe..c07b98d2a4 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; @@ -23,6 +24,7 @@ final class ClosureHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private ClosureTypeResolver $closureTypeResolver, ) { @@ -38,7 +40,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 4c9d01d194..8487a2fa9d 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; @@ -26,6 +27,7 @@ final class CoalesceHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private NonNullabilityHelper $nonNullabilityHelper, ) { @@ -82,7 +84,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 95a41ed3d3..9eefa55639 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; @@ -28,6 +29,7 @@ final class ConstFetchHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private ConstantResolver $constantResolver, ) { @@ -42,7 +44,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 2d04b85d3f..703106a37b 100644 --- a/src/Analyser/ExprHandler/EmptyHandler.php +++ b/src/Analyser/ExprHandler/EmptyHandler.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; @@ -25,6 +26,7 @@ final class EmptyHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private NonNullabilityHelper $nonNullabilityHelper, ) { @@ -69,7 +71,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 6472fcdab4..b3003ea484 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; @@ -21,6 +22,12 @@ final class ErrorSuppressHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof ErrorSuppress; @@ -30,7 +37,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 121fdc12b5..ede6dcbee0 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; @@ -25,6 +26,12 @@ final class EvalHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Eval_; @@ -40,7 +47,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 010f44b0dd..801c012472 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; @@ -24,6 +25,12 @@ final class ExitHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Exit_; @@ -47,7 +54,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 96cf7fa0f1..876adf672e 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -15,6 +15,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; @@ -79,6 +80,7 @@ final class FuncCallHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private ReflectionProvider $reflectionProvider, private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider, @@ -470,7 +472,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/IncludeHandler.php b/src/Analyser/ExprHandler/IncludeHandler.php index 4987f4db53..02f4ed54ac 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; @@ -26,6 +27,12 @@ final class IncludeHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Include_; @@ -42,7 +49,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 59d09b1c9a..ef598e8d44 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; @@ -33,6 +34,12 @@ final class InstanceofHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Instanceof_; @@ -55,7 +62,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 24e4dcab0e..f8fa79d993 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\MutatingScope; @@ -26,6 +27,7 @@ final class InterpolatedStringHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -54,7 +56,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 fe93e1fe6a..e62efcf40c 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.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\NonNullabilityHelper; @@ -32,6 +33,7 @@ final class IssetHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private NonNullabilityHelper $nonNullabilityHelper, ) { @@ -115,7 +117,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 626e6fc717..ffa95b412a 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; @@ -51,6 +52,7 @@ final class MatchHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, #[AutowiredParameter] private bool $treatPhpDocTypesAsCertain, ) @@ -472,7 +474,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 bf40faf546..498bfc23b1 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; @@ -57,6 +58,7 @@ final class MethodCallHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, private MethodCallReturnTypeHelper $methodCallReturnTypeHelper, #[AutowiredParameter(ref: '%exceptions.implicitThrows%')] @@ -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 4179b5259d..b41c5644c4 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; @@ -63,6 +64,7 @@ final class NewHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private ReflectionProvider $reflectionProvider, private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider, @@ -202,7 +204,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/NullsafeMethodCallHandler.php b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php index 13c0577f85..8e8fe6a8d9 100644 --- a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php +++ b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.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\NonNullabilityHelper; @@ -31,6 +32,7 @@ final class NullsafeMethodCallHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private NonNullabilityHelper $nonNullabilityHelper, ) { @@ -78,7 +80,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex ); $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/NullsafePropertyFetchHandler.php b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php index f5732f72c4..80c10dd795 100644 --- a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.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\NonNullabilityHelper; @@ -31,6 +32,7 @@ final class NullsafePropertyFetchHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private NonNullabilityHelper $nonNullabilityHelper, ) { @@ -70,7 +72,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 fb5c239a84..e2ff71f6d8 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; @@ -28,6 +29,12 @@ final class PipeHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Pipe; @@ -80,7 +87,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 3fab642bc4..3a85513859 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; @@ -22,6 +23,12 @@ final class PostDecHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof PostDec; @@ -40,7 +47,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 beb2d6656f..88d89d5536 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; @@ -22,6 +23,12 @@ final class PostIncHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof PostInc; @@ -40,7 +47,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 8a062f2c59..e1270e77fb 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; @@ -36,6 +37,12 @@ final class PreDecHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof PreDec; @@ -103,7 +110,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 7ab9c1eeac..0a3ddd9b97 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; @@ -37,6 +38,12 @@ final class PreIncHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof PreInc; @@ -104,7 +111,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 71f0e2d8c1..d0af380589 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\ImpurePoint; @@ -24,6 +25,12 @@ final class PrintHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Print_; @@ -39,7 +46,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/PropertyFetchHandler.php b/src/Analyser/ExprHandler/PropertyFetchHandler.php index 52e2c7cd68..c765482c6c 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; @@ -34,6 +35,7 @@ final class PropertyFetchHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private PhpVersion $phpVersion, private PropertyReflectionFinder $propertyReflectionFinder, ) @@ -77,7 +79,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 f354a32e34..ef295e71f9 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; @@ -25,6 +26,7 @@ final class ScalarHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -37,7 +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 { - 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 1438af86fa..5f001cf1a1 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; @@ -56,6 +57,7 @@ final class StaticCallHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, private MethodCallReturnTypeHelper $methodCallReturnTypeHelper, #[AutowiredParameter(ref: '%exceptions.implicitThrows%')] @@ -257,7 +259,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 a22cfb6e89..7bc5a13589 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; @@ -35,6 +36,7 @@ final class StaticPropertyFetchHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private PropertyReflectionFinder $propertyReflectionFinder, ) { @@ -76,7 +78,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 81b58b16c8..b6d8cef4ee 100644 --- a/src/Analyser/ExprHandler/TernaryHandler.php +++ b/src/Analyser/ExprHandler/TernaryHandler.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; @@ -26,6 +27,7 @@ final class TernaryHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private NodeScopeResolver $nodeScopeResolver, ) { @@ -117,7 +119,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 7a51007317..7d4dce18ee 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; @@ -24,6 +25,12 @@ final class ThrowHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Throw_; @@ -33,7 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); - return new ExpressionResult( + return $this->expressionResultFactory->create( $scope, hasYield: false, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/UnaryMinusHandler.php b/src/Analyser/ExprHandler/UnaryMinusHandler.php index 077652da19..6538d45f3f 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; @@ -23,6 +24,7 @@ final class UnaryMinusHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -37,7 +39,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 501fc6e095..d21483bad0 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; @@ -21,6 +22,12 @@ final class UnaryPlusHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof UnaryPlus; @@ -30,7 +37,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 2ce7a670fc..48f331f7cd 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; @@ -31,6 +32,12 @@ final class VariableHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Variable; @@ -86,7 +93,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 65dcbbc50f..94645c4235 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; @@ -21,6 +22,12 @@ final class AlwaysRememberedExprHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof AlwaysRememberedExpr; @@ -40,7 +47,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 2b7dc4340d..9978758998 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; @@ -21,6 +22,12 @@ final class ExistingArrayDimFetchHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof ExistingArrayDimFetch; @@ -31,7 +38,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 d609d8cfb3..c8f25c727d 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; @@ -22,6 +23,12 @@ final class FunctionCallableNodeHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof FunctionCallableNode; @@ -42,7 +49,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 a0f602a0d1..a1108ceb25 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; @@ -21,6 +22,12 @@ final class GetIterableKeyTypeExprHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof GetIterableKeyTypeExpr; @@ -31,7 +38,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 9d1e2e6dcf..d64329ced9 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; @@ -21,6 +22,12 @@ final class GetIterableValueTypeExprHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof GetIterableValueTypeExpr; @@ -31,7 +38,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 dfcc91a501..37a5359b7c 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; @@ -21,6 +22,12 @@ final class GetOffsetValueTypeExprHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof GetOffsetValueTypeExpr; @@ -31,7 +38,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 4e5ac24f80..ed248a3cdf 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; @@ -22,6 +23,12 @@ final class InstantiationCallableNodeHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof InstantiationCallableNode; @@ -42,7 +49,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 cabf8f72a8..27fb7f68cc 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; @@ -23,6 +24,12 @@ final class MethodCallableNodeHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof MethodCallableNode; @@ -45,7 +52,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 39a0572b65..8fe636c510 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; @@ -21,6 +22,12 @@ final class NativeTypeExprHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof NativeTypeExpr; @@ -31,7 +38,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 909f59a588..399d4e1127 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; @@ -24,6 +25,7 @@ final class OriginalPropertyTypeExprHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private PropertyReflectionFinder $propertyReflectionFinder, ) { @@ -39,7 +41,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 cbc691f1d9..ba6c2e4d19 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; @@ -23,6 +24,12 @@ final class SetExistingOffsetValueTypeExprHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof SetExistingOffsetValueTypeExpr; @@ -33,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/SetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php index 4bf97ae13a..848aa17653 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; @@ -23,6 +24,12 @@ final class SetOffsetValueTypeExprHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof SetOffsetValueTypeExpr; @@ -33,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/StaticMethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php index f70872144b..5f839d7062 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; @@ -23,6 +24,12 @@ final class StaticMethodCallableNodeHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof StaticMethodCallableNode; @@ -51,7 +58,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 316753bb38..89d9f0568d 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; @@ -21,6 +22,12 @@ final class TypeExprHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof TypeExpr; @@ -31,7 +38,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 1406ae5633..b8a13d7741 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; @@ -21,6 +22,12 @@ final class UnsetOffsetExprHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof UnsetOffsetExpr; @@ -31,7 +38,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 3669cc6ea5..9821a0951e 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; @@ -27,6 +28,12 @@ final class YieldFromHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof YieldFrom; @@ -48,7 +55,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 33dd84e06f..5451add22d 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; @@ -27,6 +28,12 @@ final class YieldHandler implements ExprHandler { + public function __construct( + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + public function supports(Expr $expr): bool { return $expr instanceof Yield_; @@ -78,7 +85,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 746c518953..42e49e004a 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 0000000000..f724e6fdcb --- /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, @@ -2886,7 +2887,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()); } /** @@ -3528,7 +3529,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); } /** @@ -3665,7 +3666,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 0565407424..1e7b76d600 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\Fiber\FiberNodeScopeResolver; use PHPStan\Analyser\FileAnalyser; use PHPStan\Analyser\IgnoreErrorExtensionProvider; @@ -109,6 +110,7 @@ protected function createNodeScopeResolver(): NodeScopeResolver self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), self::getContainer()->getByType(DeepNodeCloner::class), + self::getContainer()->getByType(ExpressionResultFactory::class), $this->shouldPolluteScopeWithLoopInitialAssignments(), $this->shouldPolluteScopeWithAlwaysIterableForeach(), self::getContainer()->getParameter('polluteScopeWithBlock'), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index fb793c45c9..5234372e69 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\Fiber\FiberNodeScopeResolver; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; @@ -84,6 +85,7 @@ protected static function createNodeScopeResolver(): NodeScopeResolver $container->getByType(ParameterClosureTypeExtensionProvider::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), self::getContainer()->getByType(DeepNodeCloner::class), + self::getContainer()->getByType(ExpressionResultFactory::class), $container->getParameter('polluteScopeWithLoopInitialAssignments'), $container->getParameter('polluteScopeWithAlwaysIterableForeach'), $container->getParameter('polluteScopeWithBlock'), diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 27f9daddb0..738a61d2f9 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -825,6 +825,7 @@ private function createAnalyser(): Analyser $container->getByType(ParameterClosureTypeExtensionProvider::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), $container->getByType(DeepNodeCloner::class), + $container->getByType(ExpressionResultFactory::class), false, true, true, diff --git a/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverRuleTest.php b/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverRuleTest.php index e963de6bc9..7a778f020d 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\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\Type\ParameterClosureThisExtensionProvider; @@ -129,6 +130,7 @@ protected function createNodeScopeResolver(): NodeScopeResolver self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), self::getContainer()->getByType(DeepNodeCloner::class), + self::getContainer()->getByType(ExpressionResultFactory::class), $this->shouldPolluteScopeWithLoopInitialAssignments(), $this->shouldPolluteScopeWithAlwaysIterableForeach(), self::getContainer()->getParameter('polluteScopeWithBlock'), diff --git a/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverTest.php b/tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverTest.php index 5bea8cce79..fe24a8849e 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\NodeScopeResolver; use PHPStan\DependencyInjection\Type\ParameterClosureThisExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; @@ -62,6 +63,7 @@ protected static function createNodeScopeResolver(): NodeScopeResolver $container->getByType(ParameterClosureTypeExtensionProvider::class), self::createScopeFactory($reflectionProvider, $typeSpecifier), $container->getByType(DeepNodeCloner::class), + self::getContainer()->getByType(ExpressionResultFactory::class), $container->getParameter('polluteScopeWithLoopInitialAssignments'), $container->getParameter('polluteScopeWithAlwaysIterableForeach'), $container->getParameter('polluteScopeWithBlock'), From af917b701502e361321e31cefe411751befebaa2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Mar 2026 10:56:38 +0100 Subject: [PATCH 02/11] Add Expr to ExpressionResult --- src/Analyser/ExprHandler/ArrayDimFetchHandler.php | 2 ++ src/Analyser/ExprHandler/ArrayHandler.php | 1 + src/Analyser/ExprHandler/ArrowFunctionHandler.php | 1 + src/Analyser/ExprHandler/AssignHandler.php | 7 ++++--- src/Analyser/ExprHandler/AssignOpHandler.php | 2 ++ src/Analyser/ExprHandler/BinaryOpHandler.php | 1 + src/Analyser/ExprHandler/BitwiseNotHandler.php | 1 + src/Analyser/ExprHandler/BooleanAndHandler.php | 1 + src/Analyser/ExprHandler/BooleanNotHandler.php | 1 + src/Analyser/ExprHandler/BooleanOrHandler.php | 1 + src/Analyser/ExprHandler/CastHandler.php | 1 + src/Analyser/ExprHandler/CastStringHandler.php | 1 + src/Analyser/ExprHandler/ClassConstFetchHandler.php | 1 + src/Analyser/ExprHandler/CloneHandler.php | 1 + src/Analyser/ExprHandler/ClosureHandler.php | 1 + src/Analyser/ExprHandler/CoalesceHandler.php | 1 + src/Analyser/ExprHandler/ConstFetchHandler.php | 1 + src/Analyser/ExprHandler/EmptyHandler.php | 1 + src/Analyser/ExprHandler/ErrorSuppressHandler.php | 1 + src/Analyser/ExprHandler/EvalHandler.php | 1 + src/Analyser/ExprHandler/ExitHandler.php | 1 + src/Analyser/ExprHandler/FuncCallHandler.php | 1 + src/Analyser/ExprHandler/IncludeHandler.php | 1 + src/Analyser/ExprHandler/InstanceofHandler.php | 1 + src/Analyser/ExprHandler/InterpolatedStringHandler.php | 1 + src/Analyser/ExprHandler/IssetHandler.php | 1 + src/Analyser/ExprHandler/MatchHandler.php | 1 + src/Analyser/ExprHandler/MethodCallHandler.php | 2 ++ src/Analyser/ExprHandler/NewHandler.php | 1 + src/Analyser/ExprHandler/NullsafeMethodCallHandler.php | 1 + .../ExprHandler/NullsafePropertyFetchHandler.php | 1 + 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 + src/Analyser/ExprHandler/PropertyFetchHandler.php | 1 + src/Analyser/ExprHandler/ScalarHandler.php | 1 + src/Analyser/ExprHandler/StaticCallHandler.php | 1 + src/Analyser/ExprHandler/StaticPropertyFetchHandler.php | 1 + src/Analyser/ExprHandler/TernaryHandler.php | 1 + src/Analyser/ExprHandler/ThrowHandler.php | 1 + src/Analyser/ExprHandler/UnaryMinusHandler.php | 1 + src/Analyser/ExprHandler/UnaryPlusHandler.php | 1 + src/Analyser/ExprHandler/VariableHandler.php | 1 + .../ExprHandler/Virtual/AlwaysRememberedExprHandler.php | 1 + .../ExprHandler/Virtual/ExistingArrayDimFetchHandler.php | 1 + .../ExprHandler/Virtual/FunctionCallableNodeHandler.php | 1 + .../Virtual/GetIterableKeyTypeExprHandler.php | 1 + .../Virtual/GetIterableValueTypeExprHandler.php | 1 + .../Virtual/GetOffsetValueTypeExprHandler.php | 1 + .../Virtual/InstantiationCallableNodeHandler.php | 1 + .../ExprHandler/Virtual/MethodCallableNodeHandler.php | 1 + .../ExprHandler/Virtual/NativeTypeExprHandler.php | 1 + .../Virtual/OriginalPropertyTypeExprHandler.php | 1 + .../Virtual/SetExistingOffsetValueTypeExprHandler.php | 1 + .../Virtual/SetOffsetValueTypeExprHandler.php | 1 + .../Virtual/StaticMethodCallableNodeHandler.php | 1 + src/Analyser/ExprHandler/Virtual/TypeExprHandler.php | 1 + .../ExprHandler/Virtual/UnsetOffsetExprHandler.php | 1 + src/Analyser/ExprHandler/YieldFromHandler.php | 1 + src/Analyser/ExprHandler/YieldHandler.php | 1 + src/Analyser/ExpressionResult.php | 2 ++ src/Analyser/ExpressionResultFactory.php | 3 +++ src/Analyser/NodeScopeResolver.php | 9 +++++---- 66 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index 58bc7720b5..05d74b1b74 100644 --- a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php @@ -83,6 +83,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $varResult->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), @@ -112,6 +113,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $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 85efc32a3b..138eddc11c 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -72,6 +72,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeScopeResolver->callNodeCallback($nodeCallback, new LiteralArrayNode($expr, $itemNodes), $scope, $storage); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/ArrowFunctionHandler.php b/src/Analyser/ExprHandler/ArrowFunctionHandler.php index 073e54c328..bbec4c1545 100644 --- a/src/Analyser/ExprHandler/ArrowFunctionHandler.php +++ b/src/Analyser/ExprHandler/ArrowFunctionHandler.php @@ -40,6 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $result = $nodeScopeResolver->processArrowFunctionNode($stmt, $expr, $scope, $storage, $nodeCallback, null); return $this->expressionResultFactory->create( + $expr, $result->getScope(), hasYield: $result->hasYield(), isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 4e3d843aa6..a077748939 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -147,7 +147,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($expr->expr, $scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); }, true, ); @@ -162,6 +162,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto } return $this->expressionResultFactory->create( + $expr->expr, $scope, hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), @@ -708,7 +709,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($arrayItem->value, $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), $enterExpressionAssign, ); $scope = $result->getScope(); @@ -800,7 +801,7 @@ public function processAssignVar( } // stored where processAssignVar is called - return $this->expressionResultFactory->create($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($assignedExpr, $scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } private function unwrapAssign(Expr $expr): Expr diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index 037fa7f14e..6520dfe91c 100644 --- a/src/Analyser/ExprHandler/AssignOpHandler.php +++ b/src/Analyser/ExprHandler/AssignOpHandler.php @@ -69,6 +69,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto if ($expr instanceof Expr\AssignOp\Coalesce) { $nodeScopeResolver->storeBeforeScope($storage, $expr, $originalScope); return $this->expressionResultFactory->create( + $expr, $exprResult->getScope()->mergeWith($originalScope), $exprResult->hasYield(), $exprResult->isAlwaysTerminating(), @@ -94,6 +95,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $assignResult->hasYield(), isAlwaysTerminating: $assignResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/BinaryOpHandler.php b/src/Analyser/ExprHandler/BinaryOpHandler.php index c9d584a57e..d48d3380ea 100644 --- a/src/Analyser/ExprHandler/BinaryOpHandler.php +++ b/src/Analyser/ExprHandler/BinaryOpHandler.php @@ -73,6 +73,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $rightResult->getScope(); return $this->expressionResultFactory->create( + $expr, $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 b648aa3b68..ea12c09e6e 100644 --- a/src/Analyser/ExprHandler/BitwiseNotHandler.php +++ b/src/Analyser/ExprHandler/BitwiseNotHandler.php @@ -40,6 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); return $this->expressionResultFactory->create( + $expr, $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/BooleanAndHandler.php b/src/Analyser/ExprHandler/BooleanAndHandler.php index a17f866581..9dc12218f7 100644 --- a/src/Analyser/ExprHandler/BooleanAndHandler.php +++ b/src/Analyser/ExprHandler/BooleanAndHandler.php @@ -102,6 +102,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new BooleanAndNode($expr, $leftTruthyScope), $scope, $storage, $context); return $this->expressionResultFactory->create( + $expr, $leftMergedWithRightScope, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/BooleanNotHandler.php b/src/Analyser/ExprHandler/BooleanNotHandler.php index 9869036b27..530f90a9fa 100644 --- a/src/Analyser/ExprHandler/BooleanNotHandler.php +++ b/src/Analyser/ExprHandler/BooleanNotHandler.php @@ -41,6 +41,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/BooleanOrHandler.php b/src/Analyser/ExprHandler/BooleanOrHandler.php index 9675c4547f..335f8660d4 100644 --- a/src/Analyser/ExprHandler/BooleanOrHandler.php +++ b/src/Analyser/ExprHandler/BooleanOrHandler.php @@ -86,6 +86,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new BooleanOrNode($expr, $leftFalseyScope), $scope, $storage, $context); return $this->expressionResultFactory->create( + $expr, $leftMergedWithRightScope, hasYield: $leftResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $leftResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/CastHandler.php b/src/Analyser/ExprHandler/CastHandler.php index 7f2781b732..124d3dccdf 100644 --- a/src/Analyser/ExprHandler/CastHandler.php +++ b/src/Analyser/ExprHandler/CastHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/CastStringHandler.php b/src/Analyser/ExprHandler/CastStringHandler.php index 31688f4610..835ca94f02 100644 --- a/src/Analyser/ExprHandler/CastStringHandler.php +++ b/src/Analyser/ExprHandler/CastStringHandler.php @@ -59,6 +59,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ClassConstFetchHandler.php b/src/Analyser/ExprHandler/ClassConstFetchHandler.php index d7e255e337..d16e6de301 100644 --- a/src/Analyser/ExprHandler/ClassConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ClassConstFetchHandler.php @@ -82,6 +82,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/CloneHandler.php b/src/Analyser/ExprHandler/CloneHandler.php index 14632dbe88..8b15ccde58 100644 --- a/src/Analyser/ExprHandler/CloneHandler.php +++ b/src/Analyser/ExprHandler/CloneHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); return $this->expressionResultFactory->create( + $expr, $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ClosureHandler.php b/src/Analyser/ExprHandler/ClosureHandler.php index c07b98d2a4..7f733802b3 100644 --- a/src/Analyser/ExprHandler/ClosureHandler.php +++ b/src/Analyser/ExprHandler/ClosureHandler.php @@ -41,6 +41,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $processClosureResult->applyByRefUseScope($processClosureResult->getScope()); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index 8487a2fa9d..1adb4a4d7b 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -85,6 +85,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $condResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $condResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ConstFetchHandler.php b/src/Analyser/ExprHandler/ConstFetchHandler.php index 9eefa55639..98c7c08233 100644 --- a/src/Analyser/ExprHandler/ConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ConstFetchHandler.php @@ -45,6 +45,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeScopeResolver->callNodeCallback($nodeCallback, $expr->name, $scope, $storage); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/EmptyHandler.php b/src/Analyser/ExprHandler/EmptyHandler.php index 703106a37b..8af2be1576 100644 --- a/src/Analyser/ExprHandler/EmptyHandler.php +++ b/src/Analyser/ExprHandler/EmptyHandler.php @@ -72,6 +72,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $nodeScopeResolver->lookForUnsetAllowedUndefinedExpressions($scope, $expr->expr); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ErrorSuppressHandler.php b/src/Analyser/ExprHandler/ErrorSuppressHandler.php index b3003ea484..e7f99ba878 100644 --- a/src/Analyser/ExprHandler/ErrorSuppressHandler.php +++ b/src/Analyser/ExprHandler/ErrorSuppressHandler.php @@ -38,6 +38,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context); return $this->expressionResultFactory->create( + $expr, $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/EvalHandler.php b/src/Analyser/ExprHandler/EvalHandler.php index ede6dcbee0..067dc9cc13 100644 --- a/src/Analyser/ExprHandler/EvalHandler.php +++ b/src/Analyser/ExprHandler/EvalHandler.php @@ -48,6 +48,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ExitHandler.php b/src/Analyser/ExprHandler/ExitHandler.php index 801c012472..b46c812525 100644 --- a/src/Analyser/ExprHandler/ExitHandler.php +++ b/src/Analyser/ExprHandler/ExitHandler.php @@ -55,6 +55,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: true, diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index 876adf672e..15189544ea 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -473,6 +473,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/IncludeHandler.php b/src/Analyser/ExprHandler/IncludeHandler.php index 02f4ed54ac..60a58892e5 100644 --- a/src/Analyser/ExprHandler/IncludeHandler.php +++ b/src/Analyser/ExprHandler/IncludeHandler.php @@ -50,6 +50,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope()->afterExtractCall(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/InstanceofHandler.php b/src/Analyser/ExprHandler/InstanceofHandler.php index ef598e8d44..7c5949ede2 100644 --- a/src/Analyser/ExprHandler/InstanceofHandler.php +++ b/src/Analyser/ExprHandler/InstanceofHandler.php @@ -63,6 +63,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/InterpolatedStringHandler.php b/src/Analyser/ExprHandler/InterpolatedStringHandler.php index f8fa79d993..8ca3aa5c32 100644 --- a/src/Analyser/ExprHandler/InterpolatedStringHandler.php +++ b/src/Analyser/ExprHandler/InterpolatedStringHandler.php @@ -57,6 +57,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/IssetHandler.php b/src/Analyser/ExprHandler/IssetHandler.php index e62efcf40c..83d363ba02 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.php @@ -118,6 +118,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/MatchHandler.php b/src/Analyser/ExprHandler/MatchHandler.php index ffa95b412a..dce2d77415 100644 --- a/src/Analyser/ExprHandler/MatchHandler.php +++ b/src/Analyser/ExprHandler/MatchHandler.php @@ -475,6 +475,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index 498bfc23b1..bc9dbde758 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -194,6 +194,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $isAlwaysTerminating || $argsResult->isAlwaysTerminating(); $result = $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, @@ -222,6 +223,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex if ($calledMethodScope !== null) { $scope = $scope->mergeInitializedProperties($calledMethodScope); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index b41c5644c4..b42bd6d455 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -205,6 +205,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $isAlwaysTerminating || $argsResult->isAlwaysTerminating(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php index 8e8fe6a8d9..25a1499773 100644 --- a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php +++ b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php @@ -81,6 +81,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $this->nonNullabilityHelper->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php index 80c10dd795..d919ac180b 100644 --- a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php @@ -73,6 +73,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $this->nonNullabilityHelper->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/PipeHandler.php b/src/Analyser/ExprHandler/PipeHandler.php index e2ff71f6d8..8aa1e3c982 100644 --- a/src/Analyser/ExprHandler/PipeHandler.php +++ b/src/Analyser/ExprHandler/PipeHandler.php @@ -88,6 +88,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $callResult = $nodeScopeResolver->processExprNode($stmt, $callExpr, $scope, $storage, $nodeCallback, $context); return $this->expressionResultFactory->create( + $expr, $callResult->getScope(), hasYield: $callResult->hasYield(), isAlwaysTerminating: $callResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PostDecHandler.php b/src/Analyser/ExprHandler/PostDecHandler.php index 3a85513859..24c5327ff2 100644 --- a/src/Analyser/ExprHandler/PostDecHandler.php +++ b/src/Analyser/ExprHandler/PostDecHandler.php @@ -48,6 +48,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex )->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PostIncHandler.php b/src/Analyser/ExprHandler/PostIncHandler.php index 88d89d5536..e5e55a07fe 100644 --- a/src/Analyser/ExprHandler/PostIncHandler.php +++ b/src/Analyser/ExprHandler/PostIncHandler.php @@ -48,6 +48,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex )->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PreDecHandler.php b/src/Analyser/ExprHandler/PreDecHandler.php index e1270e77fb..0d188dbe26 100644 --- a/src/Analyser/ExprHandler/PreDecHandler.php +++ b/src/Analyser/ExprHandler/PreDecHandler.php @@ -111,6 +111,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex )->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PreIncHandler.php b/src/Analyser/ExprHandler/PreIncHandler.php index 0a3ddd9b97..50664949fb 100644 --- a/src/Analyser/ExprHandler/PreIncHandler.php +++ b/src/Analyser/ExprHandler/PreIncHandler.php @@ -112,6 +112,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex )->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PrintHandler.php b/src/Analyser/ExprHandler/PrintHandler.php index d0af380589..9c46b2cf18 100644 --- a/src/Analyser/ExprHandler/PrintHandler.php +++ b/src/Analyser/ExprHandler/PrintHandler.php @@ -47,6 +47,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/PropertyFetchHandler.php b/src/Analyser/ExprHandler/PropertyFetchHandler.php index c765482c6c..4eb5048b74 100644 --- a/src/Analyser/ExprHandler/PropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/PropertyFetchHandler.php @@ -80,6 +80,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/ScalarHandler.php b/src/Analyser/ExprHandler/ScalarHandler.php index ef295e71f9..e5367c0177 100644 --- a/src/Analyser/ExprHandler/ScalarHandler.php +++ b/src/Analyser/ExprHandler/ScalarHandler.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 { return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index 5f001cf1a1..0d5e112d96 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -260,6 +260,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $isAlwaysTerminating = $isAlwaysTerminating || $argsResult->isAlwaysTerminating(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php index 7bc5a13589..3f3f2f2dd7 100644 --- a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php @@ -79,6 +79,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/TernaryHandler.php b/src/Analyser/ExprHandler/TernaryHandler.php index b6d8cef4ee..f08e7d03bf 100644 --- a/src/Analyser/ExprHandler/TernaryHandler.php +++ b/src/Analyser/ExprHandler/TernaryHandler.php @@ -120,6 +120,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $finalScope, hasYield: $ternaryCondResult->hasYield(), isAlwaysTerminating: $ternaryCondResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/ThrowHandler.php b/src/Analyser/ExprHandler/ThrowHandler.php index 7d4dce18ee..3a031658e6 100644 --- a/src/Analyser/ExprHandler/ThrowHandler.php +++ b/src/Analyser/ExprHandler/ThrowHandler.php @@ -41,6 +41,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/UnaryMinusHandler.php b/src/Analyser/ExprHandler/UnaryMinusHandler.php index 6538d45f3f..e84ce7833b 100644 --- a/src/Analyser/ExprHandler/UnaryMinusHandler.php +++ b/src/Analyser/ExprHandler/UnaryMinusHandler.php @@ -40,6 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); return $this->expressionResultFactory->create( + $expr, $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/UnaryPlusHandler.php b/src/Analyser/ExprHandler/UnaryPlusHandler.php index d21483bad0..10b5a78e34 100644 --- a/src/Analyser/ExprHandler/UnaryPlusHandler.php +++ b/src/Analyser/ExprHandler/UnaryPlusHandler.php @@ -38,6 +38,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); return $this->expressionResultFactory->create( + $expr, $exprResult->getScope(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/VariableHandler.php b/src/Analyser/ExprHandler/VariableHandler.php index 48f331f7cd..eff0cd1705 100644 --- a/src/Analyser/ExprHandler/VariableHandler.php +++ b/src/Analyser/ExprHandler/VariableHandler.php @@ -94,6 +94,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $nameResult->getScope(); } return $this->expressionResultFactory->create( + $expr, $scope, $hasYield, $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php index 94645c4235..82800a2f7b 100644 --- a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php @@ -48,6 +48,7 @@ public function processExpr( $scope = $innerResult->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $innerResult->hasYield(), isAlwaysTerminating: $innerResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php index 9978758998..be33159213 100644 --- a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php @@ -39,6 +39,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php index c8f25c727d..1292065303 100644 --- a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php @@ -50,6 +50,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php index a1108ceb25..f63fcab64e 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php @@ -39,6 +39,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php index d64329ced9..c76a428c60 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php @@ -39,6 +39,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php index 37a5359b7c..efe6e5775c 100644 --- a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php @@ -39,6 +39,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php index ed248a3cdf..2d07d3a30e 100644 --- a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php @@ -50,6 +50,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php index 27fb7f68cc..097d2d435d 100644 --- a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php @@ -53,6 +53,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php index 8fe636c510..63dd023689 100644 --- a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php @@ -39,6 +39,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php index 399d4e1127..7c6691badc 100644 --- a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php index ba6c2e4d19..2fe953546e 100644 --- a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php @@ -41,6 +41,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php index 848aa17653..4bfe1970e1 100644 --- a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php @@ -41,6 +41,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php index 5f839d7062..c0788bd7fa 100644 --- a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php @@ -59,6 +59,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php index 89d9f0568d..506a5e1bef 100644 --- a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php @@ -39,6 +39,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php index b8a13d7741..b98b5e3c7b 100644 --- a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php @@ -39,6 +39,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex // we don't need to process the inner expr return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, diff --git a/src/Analyser/ExprHandler/YieldFromHandler.php b/src/Analyser/ExprHandler/YieldFromHandler.php index 9821a0951e..0085588a40 100644 --- a/src/Analyser/ExprHandler/YieldFromHandler.php +++ b/src/Analyser/ExprHandler/YieldFromHandler.php @@ -56,6 +56,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $exprResult->getScope(); return $this->expressionResultFactory->create( + $expr, $scope, hasYield: true, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/YieldHandler.php b/src/Analyser/ExprHandler/YieldHandler.php index 5451add22d..c2159c200e 100644 --- a/src/Analyser/ExprHandler/YieldHandler.php +++ b/src/Analyser/ExprHandler/YieldHandler.php @@ -86,6 +86,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: true, isAlwaysTerminating: $isAlwaysTerminating, diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 42e49e004a..4295d25c12 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +use PhpParser\Node\Expr; use PHPStan\DependencyInjection\GenerateFactory; #[GenerateFactory(interface: ExpressionResultFactory::class)] @@ -25,6 +26,7 @@ final class ExpressionResult * @param (callable(): MutatingScope)|null $falseyScopeCallback */ public function __construct( + private Expr $expr, private MutatingScope $scope, private bool $hasYield, private bool $isAlwaysTerminating, diff --git a/src/Analyser/ExpressionResultFactory.php b/src/Analyser/ExpressionResultFactory.php index f724e6fdcb..c3d1905d77 100644 --- a/src/Analyser/ExpressionResultFactory.php +++ b/src/Analyser/ExpressionResultFactory.php @@ -2,6 +2,8 @@ namespace PHPStan\Analyser; +use PhpParser\Node\Expr; + interface ExpressionResultFactory { @@ -12,6 +14,7 @@ interface ExpressionResultFactory * @param (callable(): MutatingScope)|null $falseyScopeCallback */ public function create( + Expr $expr, MutatingScope $scope, bool $hasYield, bool $isAlwaysTerminating, diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a6fd47fef5..05c05f2888 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2510,10 +2510,11 @@ 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($expr, $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []); } return $this->expressionResultFactory->create( + $expr, $scope, hasYield: false, isAlwaysTerminating: false, @@ -2887,7 +2888,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($expr, $scope, false, $exprResult->isAlwaysTerminating(), $exprResult->getThrowPoints(), $exprResult->getImpurePoints()); } /** @@ -3529,7 +3530,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($callLike, $scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } /** @@ -3666,7 +3667,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($assignedExpr, $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), false, ); } From b2ce1a05580f70722b9c99e1f33d6da3ed703345 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Mar 2026 11:10:00 +0100 Subject: [PATCH 03/11] Introduce Type getters on ExpressionResult --- .../ExprHandler/ArrayDimFetchHandler.php | 34 +++-- src/Analyser/ExprHandler/ArrayHandler.php | 14 ++ .../ExprHandler/ArrowFunctionHandler.php | 1 + src/Analyser/ExprHandler/AssignHandler.php | 14 +- src/Analyser/ExprHandler/BinaryOpHandler.php | 132 ++++++++++++++++++ .../ExprHandler/BitwiseNotHandler.php | 8 ++ .../ExprHandler/BooleanAndHandler.php | 19 ++- .../ExprHandler/BooleanNotHandler.php | 8 ++ src/Analyser/ExprHandler/BooleanOrHandler.php | 19 ++- src/Analyser/ExprHandler/CastHandler.php | 14 ++ .../ExprHandler/CastStringHandler.php | 8 ++ .../ExprHandler/ClassConstFetchHandler.php | 20 +++ src/Analyser/ExprHandler/CloneHandler.php | 1 + src/Analyser/ExprHandler/CoalesceHandler.php | 17 +++ .../ExprHandler/ConstFetchHandler.php | 38 +++++ .../ExprHandler/ErrorSuppressHandler.php | 1 + src/Analyser/ExprHandler/EvalHandler.php | 1 + src/Analyser/ExprHandler/ExitHandler.php | 1 + .../FirstClassCallableFuncCallHandler.php | 35 ++++- .../FirstClassCallableMethodCallHandler.php | 43 +++++- .../FirstClassCallableNewHandler.php | 23 ++- .../FirstClassCallableStaticCallHandler.php | 31 +++- src/Analyser/ExprHandler/IncludeHandler.php | 1 + .../ExprHandler/InstanceofHandler.php | 49 ++++++- .../ExprHandler/InterpolatedStringHandler.php | 25 +++- src/Analyser/ExprHandler/MatchHandler.php | 13 ++ .../ExprHandler/NullsafeMethodCallHandler.php | 17 +++ .../NullsafePropertyFetchHandler.php | 17 +++ src/Analyser/ExprHandler/PipeHandler.php | 1 + src/Analyser/ExprHandler/PostDecHandler.php | 1 + src/Analyser/ExprHandler/PostIncHandler.php | 1 + src/Analyser/ExprHandler/PreDecHandler.php | 51 +++++++ src/Analyser/ExprHandler/PreIncHandler.php | 51 +++++++ src/Analyser/ExprHandler/PrintHandler.php | 1 + .../ExprHandler/PropertyFetchHandler.php | 25 ++++ src/Analyser/ExprHandler/ScalarHandler.php | 1 + .../StaticPropertyFetchHandler.php | 29 ++++ src/Analyser/ExprHandler/TernaryHandler.php | 51 +++++-- src/Analyser/ExprHandler/ThrowHandler.php | 1 + .../ExprHandler/UnaryMinusHandler.php | 8 ++ src/Analyser/ExprHandler/UnaryPlusHandler.php | 1 + src/Analyser/ExprHandler/VariableHandler.php | 26 +++- .../Virtual/AlwaysRememberedExprHandler.php | 1 + .../Virtual/ExistingArrayDimFetchHandler.php | 5 +- .../Virtual/FunctionCallableNodeHandler.php | 1 + .../Virtual/GetIterableKeyTypeExprHandler.php | 4 +- .../GetIterableValueTypeExprHandler.php | 4 +- .../Virtual/GetOffsetValueTypeExprHandler.php | 5 +- .../InstantiationCallableNodeHandler.php | 1 + .../Virtual/MethodCallableNodeHandler.php | 1 + .../Virtual/NativeTypeExprHandler.php | 1 + .../OriginalPropertyTypeExprHandler.php | 27 +++- .../SetExistingOffsetValueTypeExprHandler.php | 23 ++- .../Virtual/SetOffsetValueTypeExprHandler.php | 23 ++- .../StaticMethodCallableNodeHandler.php | 1 + .../ExprHandler/Virtual/TypeExprHandler.php | 1 + .../Virtual/UnsetOffsetExprHandler.php | 5 +- src/Analyser/ExprHandler/YieldFromHandler.php | 8 ++ src/Analyser/ExprHandler/YieldHandler.php | 14 ++ src/Analyser/ExpressionResult.php | 130 +++++++++++++++++ src/Analyser/ExpressionResultFactory.php | 3 + src/Analyser/MutatingScope.php | 2 +- src/Analyser/NodeScopeResolver.php | 41 +++++- src/Analyser/RicherScopeGetTypeHelper.php | 22 ++- .../Properties/PropertyReflectionFinder.php | 39 ++++-- 65 files changed, 1132 insertions(+), 82 deletions(-) diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index 05d74b1b74..f4f70e4018 100644 --- a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php @@ -85,6 +85,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new NeverType(), hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), @@ -100,21 +101,38 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = array_merge($dimResult->getImpurePoints(), $varResult->getImpurePoints()); $scope = $varResult->getScope(); + $offsetGetResult = $nodeScopeResolver->processExprNode( + $stmt, + new MethodCall($expr->var, new Identifier('offsetGet'), [new Arg($expr->dim)]), + $scope, + new ExpressionResultStorage(), + new NoopNodeCallback(), + $context->enterDeep(), + ); + $varType = $scope->getType($expr->var); if (!$varType->isArray()->yes() && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) { - $throwPoints = array_merge($throwPoints, $nodeScopeResolver->processExprNode( - $stmt, - new MethodCall($expr->var, 'offsetGet'), - $scope, - $storage, - new NoopNodeCallback(), - $context, - )->getThrowPoints()); + $throwPoints = array_merge($throwPoints, $offsetGetResult->getThrowPoints()); } return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($varResult, $dimResult, $offsetGetResult): Type { + $varType = $varResult->getTypeForScope($scope); + if ($varType instanceof NeverType) { + return $varType; + } + + if ( + !$varType->isArray()->yes() + && (new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes() + ) { + return $offsetGetResult->getTypeForScope($scope); + } + + return $varType->getOffsetValueType($dimResult->getTypeForScope($scope)); + }, 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 138eddc11c..33e3933180 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -12,12 +12,14 @@ use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\LiteralArrayItem; use PHPStan\Node\LiteralArrayNode; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\Type; use function array_merge; +use function spl_object_id; /** * @implements ExprHandler @@ -50,11 +52,14 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $throwPoints = []; $impurePoints = []; $isAlwaysTerminating = false; + /** @var array */ + $itemResults = []; foreach ($expr->items as $arrayItem) { $itemNodes[] = new LiteralArrayItem($scope, $arrayItem); $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()); @@ -63,6 +68,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()); @@ -74,6 +80,14 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: fn (Expr $expr, MutatingScope $scope) => $this->initializerExprTypeResolver->getArrayType($expr, static function (Expr $e) use ($itemResults, $scope, $nodeScopeResolver, $stmt): Type { + $id = spl_object_id($e); + if (isset($itemResults[$id])) { + return $itemResults[$id]->getTypeForScope($scope); + } + + return $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + }), hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ArrowFunctionHandler.php b/src/Analyser/ExprHandler/ArrowFunctionHandler.php index bbec4c1545..fb55895189 100644 --- a/src/Analyser/ExprHandler/ArrowFunctionHandler.php +++ b/src/Analyser/ExprHandler/ArrowFunctionHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $result->getScope(), + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $result->getTypeForScope($scope), hasYield: $result->hasYield(), isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index a077748939..c8293cb862 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -147,7 +147,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $scope = $scope->exitExpressionAssign($expr->expr); } - return $this->expressionResultFactory->create($expr->expr, $scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($expr->expr, $scope, typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $result->getTypeForScope($scope), hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints); }, true, ); @@ -164,6 +164,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto return $this->expressionResultFactory->create( $expr->expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $result->getTypeForScope($scope), hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), throwPoints: $result->getThrowPoints(), @@ -195,10 +196,12 @@ public function processAssignVar( $throwPoints = []; $impurePoints = []; $isAlwaysTerminating = false; + $exprCallbackResult = null; $isAssignOp = $assignedExpr instanceof Expr\AssignOp && !$enterExpressionAssign; if ($var instanceof Variable) { $nodeScopeResolver->storeBeforeScope($storage, $var, $scope); $result = $processExprCallback($scope); + $exprCallbackResult = $result; $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); @@ -385,6 +388,7 @@ public function processAssignVar( // 3. eval assigned expr $result = $processExprCallback($scope); + $exprCallbackResult = $result; $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -511,6 +515,7 @@ public function processAssignVar( $scopeBeforeAssignEval = $scope; $result = $processExprCallback($scope); + $exprCallbackResult = $result; $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -624,6 +629,7 @@ public function processAssignVar( $scopeBeforeAssignEval = $scope; $result = $processExprCallback($scope); + $exprCallbackResult = $result; $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -670,6 +676,7 @@ public function processAssignVar( } elseif ($var instanceof List_) { $nodeScopeResolver->storeBeforeScope($storage, $var, $scope); $result = $processExprCallback($scope); + $exprCallbackResult = $result; $hasYield = $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -709,7 +716,7 @@ public function processAssignVar( new GetOffsetValueTypeExpr($assignedExpr, $dimExpr), $nodeCallback, $context, - fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($arrayItem->value, $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), + fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($arrayItem->value, $scope, typeCallback: static fn () => new MixedType(), hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), $enterExpressionAssign, ); $scope = $result->getScope(); @@ -793,6 +800,7 @@ public function processAssignVar( $isAlwaysTerminating = $varResult->isAlwaysTerminating(); $scope = $varResult->getScope(); $result = $processExprCallback($scope); + $exprCallbackResult = $result; $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); @@ -801,7 +809,7 @@ public function processAssignVar( } // stored where processAssignVar is called - return $this->expressionResultFactory->create($assignedExpr, $scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create($assignedExpr, $scope, typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $exprCallbackResult->getTypeForScope($scope), hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, impurePoints: $impurePoints); } private function unwrapAssign(Expr $expr): Expr diff --git a/src/Analyser/ExprHandler/BinaryOpHandler.php b/src/Analyser/ExprHandler/BinaryOpHandler.php index d48d3380ea..24e257140e 100644 --- a/src/Analyser/ExprHandler/BinaryOpHandler.php +++ b/src/Analyser/ExprHandler/BinaryOpHandler.php @@ -17,6 +17,7 @@ use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\Analyser\RicherScopeGetTypeHelper; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; @@ -75,6 +76,137 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($leftResult, $rightResult, $nodeScopeResolver, $stmt): Type { + $leftType = $leftResult->getTypeForScope($scope); + $rightType = $rightResult->getTypeForScope($scope); + $getType = static function (Expr $e) use ($expr, $leftResult, $rightResult, $scope, $nodeScopeResolver, $stmt): Type { + if ($e === $expr->left) { + return $leftResult->getTypeForScope($scope); + } + if ($e === $expr->right) { + return $rightResult->getTypeForScope($scope); + } + + return $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + }; + + if ($expr instanceof BinaryOp\Smaller) { + return $leftType->isSmallerThan($rightType, $this->phpVersion)->toBooleanType(); + } + + if ($expr instanceof BinaryOp\SmallerOrEqual) { + return $leftType->isSmallerThanOrEqual($rightType, $this->phpVersion)->toBooleanType(); + } + + if ($expr instanceof BinaryOp\Greater) { + return $rightType->isSmallerThan($leftType, $this->phpVersion)->toBooleanType(); + } + + if ($expr instanceof BinaryOp\GreaterOrEqual) { + return $rightType->isSmallerThanOrEqual($leftType, $this->phpVersion)->toBooleanType(); + } + + if ($expr instanceof BinaryOp\Equal) { + if ( + $expr->left instanceof Variable + && is_string($expr->left->name) + && $expr->right instanceof Variable + && is_string($expr->right->name) + && $expr->left->name === $expr->right->name + ) { + return new ConstantBooleanType(true); + } + + return $this->initializerExprTypeResolver->resolveEqualType($leftType, $rightType)->type; + } + + if ($expr instanceof BinaryOp\NotEqual) { + $equalType = $this->initializerExprTypeResolver->resolveEqualType($leftType, $rightType)->type; + if ($equalType instanceof ConstantBooleanType) { + return new ConstantBooleanType(!$equalType->getValue()); + } + + return new BooleanType(); + } + + if ($expr instanceof BinaryOp\Identical) { + return $this->richerScopeGetTypeHelper->getIdenticalResultWithTypes($scope, $expr, $leftType, $rightType)->type; + } + + if ($expr instanceof BinaryOp\NotIdentical) { + return $this->richerScopeGetTypeHelper->getNotIdenticalResultWithTypes($scope, $expr, $leftType, $rightType)->type; + } + + if ($expr instanceof BinaryOp\LogicalXor) { + $leftBooleanType = $leftType->toBoolean(); + $rightBooleanType = $rightType->toBoolean(); + + if ( + $leftBooleanType instanceof ConstantBooleanType + && $rightBooleanType instanceof ConstantBooleanType + ) { + return new ConstantBooleanType( + $leftBooleanType->getValue() xor $rightBooleanType->getValue(), + ); + } + + return new BooleanType(); + } + + if ($expr instanceof BinaryOp\Spaceship) { + return $this->initializerExprTypeResolver->getSpaceshipType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\Concat) { + return $this->initializerExprTypeResolver->getConcatType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\BitwiseAnd) { + return $this->initializerExprTypeResolver->getBitwiseAndType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\BitwiseOr) { + return $this->initializerExprTypeResolver->getBitwiseOrType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\BitwiseXor) { + return $this->initializerExprTypeResolver->getBitwiseXorType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\Div) { + return $this->initializerExprTypeResolver->getDivType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\Mod) { + return $this->initializerExprTypeResolver->getModType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\Plus) { + return $this->initializerExprTypeResolver->getPlusType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\Minus) { + return $this->initializerExprTypeResolver->getMinusType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\Mul) { + return $this->initializerExprTypeResolver->getMulType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\Pow) { + return $this->initializerExprTypeResolver->getPowType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\ShiftLeft) { + return $this->initializerExprTypeResolver->getShiftLeftType($expr->left, $expr->right, $getType); + } + + if ($expr instanceof BinaryOp\ShiftRight) { + return $this->initializerExprTypeResolver->getShiftRightType($expr->left, $expr->right, $getType); + } + + throw new ShouldNotHappenException(sprintf('Unhandled %s', get_class($expr))); + }, 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 ea12c09e6e..1705bc7c74 100644 --- a/src/Analyser/ExprHandler/BitwiseNotHandler.php +++ b/src/Analyser/ExprHandler/BitwiseNotHandler.php @@ -12,6 +12,7 @@ use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\Type; @@ -42,6 +43,13 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $exprResult->getScope(), + typeCallback: fn (Expr $uninteresting, MutatingScope $scope) => $this->initializerExprTypeResolver->getBitwiseNotType($expr->expr, static function (Expr $e) use ($expr, $exprResult, $nodeScopeResolver, $stmt, $scope): Type { + if ($e === $expr->expr) { + return $exprResult->getTypeForScope($scope); + } + + return $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($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 9dc12218f7..dfd1c06729 100644 --- a/src/Analyser/ExprHandler/BooleanAndHandler.php +++ b/src/Analyser/ExprHandler/BooleanAndHandler.php @@ -92,7 +92,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $leftResult = $nodeScopeResolver->processExprNode($stmt, $expr->left, $scope, $storage, $nodeCallback, $context->enterDeep()); $leftTruthyScope = $leftResult->getTruthyScope(); $rightResult = $nodeScopeResolver->processExprNode($stmt, $expr->right, $leftTruthyScope, $storage, $nodeCallback, $context); - $rightExprType = $rightResult->getScope()->getType($expr->right); + $rightExprType = $rightResult->getType(); if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { $leftMergedWithRightScope = $leftResult->getFalseyScope(); } else { @@ -104,6 +104,23 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $leftMergedWithRightScope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($leftResult, $rightResult): Type { + $leftBooleanType = $leftResult->getTypeForScope($scope)->toBoolean(); + if ($leftBooleanType->isFalse()->yes()) { + return new ConstantBooleanType(false); + } + + $rightBooleanType = $rightResult->getTypeForScope($scope)->toBoolean(); + if ($rightBooleanType->isFalse()->yes()) { + return new ConstantBooleanType(false); + } + + if ($leftBooleanType->isTrue()->yes() && $rightBooleanType->isTrue()->yes()) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + }, 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 530f90a9fa..ae15630e4d 100644 --- a/src/Analyser/ExprHandler/BooleanNotHandler.php +++ b/src/Analyser/ExprHandler/BooleanNotHandler.php @@ -43,6 +43,14 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($exprResult): Type { + $exprBooleanType = $exprResult->getTypeForScope($scope)->toBoolean(); + if ($exprBooleanType instanceof ConstantBooleanType) { + return new ConstantBooleanType(!$exprBooleanType->getValue()); + } + + return new BooleanType(); + }, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/BooleanOrHandler.php b/src/Analyser/ExprHandler/BooleanOrHandler.php index 335f8660d4..d82450f589 100644 --- a/src/Analyser/ExprHandler/BooleanOrHandler.php +++ b/src/Analyser/ExprHandler/BooleanOrHandler.php @@ -76,7 +76,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $leftResult = $nodeScopeResolver->processExprNode($stmt, $expr->left, $scope, $storage, $nodeCallback, $context->enterDeep()); $leftFalseyScope = $leftResult->getFalseyScope(); $rightResult = $nodeScopeResolver->processExprNode($stmt, $expr->right, $leftFalseyScope, $storage, $nodeCallback, $context); - $rightExprType = $rightResult->getScope()->getType($expr->right); + $rightExprType = $rightResult->getType(); if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { $leftMergedWithRightScope = $leftResult->getTruthyScope(); } else { @@ -88,6 +88,23 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $leftMergedWithRightScope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($leftResult, $rightResult): Type { + $leftBooleanType = $leftResult->getTypeForScope($scope)->toBoolean(); + if ($leftBooleanType->isTrue()->yes()) { + return new ConstantBooleanType(true); + } + + $rightBooleanType = $rightResult->getTypeForScope($scope)->toBoolean(); + if ($rightBooleanType->isTrue()->yes()) { + return new ConstantBooleanType(true); + } + + if ($leftBooleanType->isFalse()->yes() && $rightBooleanType->isFalse()->yes()) { + return new ConstantBooleanType(false); + } + + return new BooleanType(); + }, 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 124d3dccdf..e13f129ec2 100644 --- a/src/Analyser/ExprHandler/CastHandler.php +++ b/src/Analyser/ExprHandler/CastHandler.php @@ -12,6 +12,7 @@ use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\NullType; @@ -44,6 +45,19 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $uninteresting, MutatingScope $scope) use ($expr, $exprResult, $nodeScopeResolver, $stmt): Type { + if ($expr instanceof Cast\Unset_) { + return new NullType(); + } + + return $this->initializerExprTypeResolver->getCastType($expr, static function (Expr $e) use ($expr, $exprResult, $nodeScopeResolver, $stmt, $scope): Type { + if ($e === $expr->expr) { + return $exprResult->getTypeForScope($scope); + } + + return $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + }); + }, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/CastStringHandler.php b/src/Analyser/ExprHandler/CastStringHandler.php index 835ca94f02..c48a44444c 100644 --- a/src/Analyser/ExprHandler/CastStringHandler.php +++ b/src/Analyser/ExprHandler/CastStringHandler.php @@ -13,6 +13,7 @@ use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\Type; @@ -61,6 +62,13 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: fn (Expr $uninteresting, MutatingScope $scope) => $this->initializerExprTypeResolver->getCastType($expr, static function (Expr $e) use ($expr, $exprResult, $nodeScopeResolver, $stmt, $scope): Type { + if ($e === $expr->expr) { + return $exprResult->getTypeForScope($scope); + } + + return $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + }), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/ClassConstFetchHandler.php b/src/Analyser/ExprHandler/ClassConstFetchHandler.php index d16e6de301..5886f38e02 100644 --- a/src/Analyser/ExprHandler/ClassConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ClassConstFetchHandler.php @@ -13,6 +13,7 @@ use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\MixedType; @@ -59,6 +60,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = []; $isAlwaysTerminating = false; + $classResult = null; if ($expr->class instanceof Expr) { $classResult = $nodeScopeResolver->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $classResult->getScope(); @@ -84,6 +86,24 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($classResult, $nodeScopeResolver, $stmt): Type { + if (!$expr->name instanceof Identifier) { + return new MixedType(); + } + + return $this->initializerExprTypeResolver->getClassConstFetchTypeByReflection( + $expr->class, + $expr->name->name, + $scope->isInClass() ? $scope->getClassReflection() : null, + static function (Expr $e) use ($expr, $classResult, $scope, $nodeScopeResolver, $stmt): Type { + if ($classResult !== null && $e === $expr->class) { + return $classResult->getTypeForScope($scope); + } + + return $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + }, + ); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/CloneHandler.php b/src/Analyser/ExprHandler/CloneHandler.php index 8b15ccde58..aa15ab1512 100644 --- a/src/Analyser/ExprHandler/CloneHandler.php +++ b/src/Analyser/ExprHandler/CloneHandler.php @@ -44,6 +44,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $exprResult->getScope(), + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => TypeTraverser::map(TypeCombinator::intersect($exprResult->getTypeForScope($scope), new ObjectWithoutClassType()), new CloneTypeTraverser()), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index 1adb4a4d7b..4eaa2b8aa2 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -87,6 +87,23 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($condResult, $rightResult): Type { + $leftType = $condResult->getTypeForScope($scope); + $rightType = $rightResult->getTypeForScope($scope); + + if ($leftType->isNull()->yes()) { + return $rightType; + } + + if (!TypeCombinator::containsNull($leftType)) { + return $leftType; + } + + return TypeCombinator::union( + TypeCombinator::removeNull($leftType), + $rightType, + ); + }, 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 98c7c08233..3154e256c3 100644 --- a/src/Analyser/ExprHandler/ConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ConstFetchHandler.php @@ -44,9 +44,47 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $nodeScopeResolver->callNodeCallback($nodeCallback, $expr->name, $scope, $storage); + $constName = (string) $expr->name; + $loweredConstName = strtolower($constName); + if ($loweredConstName === 'true') { + $constType = new ConstantBooleanType(true); + } elseif ($loweredConstName === 'false') { + $constType = new ConstantBooleanType(false); + } elseif ($loweredConstName === 'null') { + $constType = new NullType(); + } else { + $namespacedName = null; + if (!$expr->name->isFullyQualified() && $scope->getNamespace() !== null) { + $namespacedName = new FullyQualified([$scope->getNamespace(), $expr->name->toString()]); + } + $globalName = new FullyQualified($expr->name->toString()); + + $constType = $this->constantResolver->resolveConstant($expr->name, $scope) ?? new ErrorType(); + + $names = [$namespacedName, $globalName]; + } + return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($constType, $names) { + if ($names !== null) { + foreach ($names as $name) { + if ($name === null) { + continue; + } + $constFetch = new ConstFetch($name); + if ($scope->hasExpressionType($constFetch)->yes()) { + return $this->constantResolver->resolveConstantType( + $name->toString(), + $scope->expressionTypes[$scope->getNodeKey($constFetch)]->getType(), + ); + } + } + } + + return $constType; + }, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/ErrorSuppressHandler.php b/src/Analyser/ExprHandler/ErrorSuppressHandler.php index e7f99ba878..7a55592df8 100644 --- a/src/Analyser/ExprHandler/ErrorSuppressHandler.php +++ b/src/Analyser/ExprHandler/ErrorSuppressHandler.php @@ -40,6 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $exprResult->getScope(), + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $exprResult->getTypeForScope($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 067dc9cc13..8dac4e1223 100644 --- a/src/Analyser/ExprHandler/EvalHandler.php +++ b/src/Analyser/ExprHandler/EvalHandler.php @@ -50,6 +50,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new MixedType(), 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 b46c812525..15ab3e3d4a 100644 --- a/src/Analyser/ExprHandler/ExitHandler.php +++ b/src/Analyser/ExprHandler/ExitHandler.php @@ -57,6 +57,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new NonAcceptingNeverType(), hasYield: $hasYield, isAlwaysTerminating: true, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/FirstClassCallableFuncCallHandler.php b/src/Analyser/ExprHandler/FirstClassCallableFuncCallHandler.php index 0396e0883b..65ac62a779 100644 --- a/src/Analyser/ExprHandler/FirstClassCallableFuncCallHandler.php +++ b/src/Analyser/ExprHandler/FirstClassCallableFuncCallHandler.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; @@ -15,7 +16,6 @@ use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -27,6 +27,7 @@ final class FirstClassCallableFuncCallHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -47,8 +48,36 @@ public function processExpr( ExpressionContext $context, ): ExpressionResult { - // handled in NodeScopeResolver before ExprHandlers are called - throw new ShouldNotHappenException(); + $nameResult = null; + if ($expr->name instanceof Expr) { + $nameResult = $nodeScopeResolver->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); + $scope = $nameResult->getScope(); + } + + return $this->expressionResultFactory->create( + $expr, + $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($nameResult): Type { + if ($nameResult !== null) { + $callableType = $nameResult->getTypeForScope($scope); + if (!$callableType->isCallable()->yes()) { + return new ObjectType(Closure::class); + } + + return $this->initializerExprTypeResolver->createFirstClassCallable( + null, + $callableType->getCallableParametersAcceptors($scope), + $scope->nativeTypesPromoted, + ); + } + + return $this->initializerExprTypeResolver->getFirstClassCallableType($expr, InitializerExprContext::fromScope($scope), $scope->nativeTypesPromoted); + }, + hasYield: false, + isAlwaysTerminating: false, + throwPoints: $nameResult !== null ? $nameResult->getThrowPoints() : [], + impurePoints: $nameResult !== null ? $nameResult->getImpurePoints() : [], + ); } public function resolveType(MutatingScope $scope, Expr $expr): Type diff --git a/src/Analyser/ExprHandler/FirstClassCallableMethodCallHandler.php b/src/Analyser/ExprHandler/FirstClassCallableMethodCallHandler.php index 5b6a283c9b..20c6209392 100644 --- a/src/Analyser/ExprHandler/FirstClassCallableMethodCallHandler.php +++ b/src/Analyser/ExprHandler/FirstClassCallableMethodCallHandler.php @@ -9,15 +9,16 @@ 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; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; +use function array_merge; /** * @implements ExprHandler @@ -27,6 +28,7 @@ final class FirstClassCallableMethodCallHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -47,8 +49,43 @@ public function processExpr( ExpressionContext $context, ): ExpressionResult { - // handled in NodeScopeResolver before ExprHandlers are called - throw new ShouldNotHappenException(); + $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); + $scope = $varResult->getScope(); + $throwPoints = $varResult->getThrowPoints(); + $impurePoints = $varResult->getImpurePoints(); + + if (!$expr->name instanceof Identifier) { + $nameResult = $nodeScopeResolver->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); + $scope = $nameResult->getScope(); + $throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); + $impurePoints = array_merge($impurePoints, $nameResult->getImpurePoints()); + } + + return $this->expressionResultFactory->create( + $expr, + $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($varResult): Type { + if (!$expr->name instanceof Identifier) { + return new ObjectType(Closure::class); + } + + $varType = $varResult->getTypeForScope($scope); + $method = $scope->getMethodReflection($varType, $expr->name->toString()); + if ($method === null) { + return new ObjectType(Closure::class); + } + + return $this->initializerExprTypeResolver->createFirstClassCallable( + $method, + $method->getVariants(), + $scope->nativeTypesPromoted, + ); + }, + hasYield: false, + isAlwaysTerminating: false, + throwPoints: $throwPoints, + impurePoints: $impurePoints, + ); } public function resolveType(MutatingScope $scope, Expr $expr): Type diff --git a/src/Analyser/ExprHandler/FirstClassCallableNewHandler.php b/src/Analyser/ExprHandler/FirstClassCallableNewHandler.php index 647ed5b7a9..509b97772b 100644 --- a/src/Analyser/ExprHandler/FirstClassCallableNewHandler.php +++ b/src/Analyser/ExprHandler/FirstClassCallableNewHandler.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt\Class_; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; +use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; @@ -15,7 +16,6 @@ use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\Type; /** @@ -26,6 +26,7 @@ final class FirstClassCallableNewHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -46,8 +47,24 @@ public function processExpr( ExpressionContext $context, ): ExpressionResult { - // handled in NodeScopeResolver before ExprHandlers are called - throw new ShouldNotHappenException(); + $throwPoints = []; + $impurePoints = []; + if ($expr->class instanceof Expr) { + $classResult = $nodeScopeResolver->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); + $scope = $classResult->getScope(); + $throwPoints = $classResult->getThrowPoints(); + $impurePoints = $classResult->getImpurePoints(); + } + + return $this->expressionResultFactory->create( + $expr, + $scope, + typeCallback: fn (Expr $expr, MutatingScope $scope) => $this->initializerExprTypeResolver->getFirstClassCallableType($expr, InitializerExprContext::fromScope($scope), $scope->nativeTypesPromoted), + hasYield: false, + isAlwaysTerminating: false, + throwPoints: $throwPoints, + impurePoints: $impurePoints, + ); } public function resolveType(MutatingScope $scope, Expr $expr): Type diff --git a/src/Analyser/ExprHandler/FirstClassCallableStaticCallHandler.php b/src/Analyser/ExprHandler/FirstClassCallableStaticCallHandler.php index 0a5261f1f6..756bb05778 100644 --- a/src/Analyser/ExprHandler/FirstClassCallableStaticCallHandler.php +++ b/src/Analyser/ExprHandler/FirstClassCallableStaticCallHandler.php @@ -4,9 +4,11 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Identifier; 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; @@ -14,8 +16,8 @@ use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\Type; +use function array_merge; /** * @implements ExprHandler @@ -25,6 +27,7 @@ final class FirstClassCallableStaticCallHandler implements ExprHandler { public function __construct( + private ExpressionResultFactory $expressionResultFactory, private InitializerExprTypeResolver $initializerExprTypeResolver, ) { @@ -45,8 +48,30 @@ public function processExpr( ExpressionContext $context, ): ExpressionResult { - // handled in NodeScopeResolver before ExprHandlers are called - throw new ShouldNotHappenException(); + $throwPoints = []; + $impurePoints = []; + if ($expr->class instanceof Expr) { + $classResult = $nodeScopeResolver->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); + $scope = $classResult->getScope(); + $throwPoints = $classResult->getThrowPoints(); + $impurePoints = $classResult->getImpurePoints(); + } + if (!$expr->name instanceof Identifier) { + $nameResult = $nodeScopeResolver->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); + $scope = $nameResult->getScope(); + $throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); + $impurePoints = array_merge($impurePoints, $nameResult->getImpurePoints()); + } + + return $this->expressionResultFactory->create( + $expr, + $scope, + typeCallback: fn (Expr $expr, MutatingScope $scope) => $this->initializerExprTypeResolver->getFirstClassCallableType($expr, InitializerExprContext::fromScope($scope), $scope->nativeTypesPromoted), + hasYield: false, + isAlwaysTerminating: false, + throwPoints: $throwPoints, + impurePoints: $impurePoints, + ); } public function resolveType(MutatingScope $scope, Expr $expr): Type diff --git a/src/Analyser/ExprHandler/IncludeHandler.php b/src/Analyser/ExprHandler/IncludeHandler.php index 60a58892e5..e28a7c2258 100644 --- a/src/Analyser/ExprHandler/IncludeHandler.php +++ b/src/Analyser/ExprHandler/IncludeHandler.php @@ -52,6 +52,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new MixedType(), 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 7c5949ede2..abd20c1d2a 100644 --- a/src/Analyser/ExprHandler/InstanceofHandler.php +++ b/src/Analyser/ExprHandler/InstanceofHandler.php @@ -53,7 +53,19 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = $exprResult->getImpurePoints(); $isAlwaysTerminating = $exprResult->isAlwaysTerminating(); $scope = $exprResult->getScope(); - if (!$expr->class instanceof Name) { + $classTypeFromName = null; + $classResult = null; + if ($expr->class instanceof Name) { + $unresolvedClassName = $expr->class->toString(); + if ( + strtolower($unresolvedClassName) === 'static' + && $scope->isInClass() + ) { + $classTypeFromName = new StaticType($scope->getClassReflection()); + } else { + $classTypeFromName = new ObjectType($scope->resolveName($expr->class)); + } + } else { $classResult = $nodeScopeResolver->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $classResult->getScope(); $hasYield = $hasYield || $classResult->hasYield(); @@ -65,6 +77,41 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($exprResult, $classResult, $classTypeFromName): Type { + $expressionType = $exprResult->getTypeForScope($scope); + if ( + $scope->isInTrait() + && TypeUtils::findThisType($expressionType) !== null + ) { + return new BooleanType(); + } + if ($expressionType instanceof NeverType) { + return new ConstantBooleanType(false); + } + + $uncertainty = false; + if ($classTypeFromName !== null) { + $classType = $classTypeFromName; + } else { + $classType = $classResult->getTypeForScope($scope); + $traverser = new InstanceOfClassTypeTraverser(); + $classType = TypeTraverser::map($classType, $traverser); + $uncertainty = $traverser->getUncertainty(); + } + + if ($classType->isSuperTypeOf(new MixedType())->yes()) { + return new BooleanType(); + } + + $isSuperType = $classType->isSuperTypeOf($expressionType); + if ($isSuperType->no()) { + return new ConstantBooleanType(false); + } elseif ($isSuperType->yes() && !$uncertainty) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/InterpolatedStringHandler.php b/src/Analyser/ExprHandler/InterpolatedStringHandler.php index 8ca3aa5c32..0bbdb294c0 100644 --- a/src/Analyser/ExprHandler/InterpolatedStringHandler.php +++ b/src/Analyser/ExprHandler/InterpolatedStringHandler.php @@ -44,11 +44,14 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $throwPoints = []; $impurePoints = []; $isAlwaysTerminating = false; - foreach ($expr->parts as $part) { - if (!$part instanceof Expr) { + $partResults = []; + foreach ($expr->parts as $i => $part) { + if (!($part instanceof Expr)) { continue; } + $partResult = $nodeScopeResolver->processExprNode($stmt, $part, $scope, $storage, $nodeCallback, $context->enterDeep()); + $partResults[$i] = $partResult; $hasYield = $hasYield || $partResult->hasYield(); $throwPoints = array_merge($throwPoints, $partResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $partResult->getImpurePoints()); @@ -59,6 +62,24 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $uninteresting, MutatingScope $scope) use ($expr, $partResults): Type { + $resultType = null; + foreach ($expr->parts as $i => $part) { + if ($part instanceof InterpolatedStringPart) { + $partType = new ConstantStringType($part->value); + } else { + $partType = $partResults[$i]->getTypeForScope($scope)->toString(); + } + if ($resultType === null) { + $resultType = $partType; + continue; + } + + $resultType = $this->initializerExprTypeResolver->resolveConcatType($resultType, $partType); + } + + return $resultType ?? new ConstantStringType(''); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/MatchHandler.php b/src/Analyser/ExprHandler/MatchHandler.php index dce2d77415..d62ee00a2b 100644 --- a/src/Analyser/ExprHandler/MatchHandler.php +++ b/src/Analyser/ExprHandler/MatchHandler.php @@ -201,6 +201,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $arms = $expr->arms; $armCondsToSkip = []; $armBodyScopes = []; + /** @var ExpressionResult[] */ + $armBodyResults = []; if ($condType->isEnum()->yes()) { // enum match analysis would work even without this if branch // but would be much slower @@ -336,6 +338,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, ExpressionContext::createTopLevel(), ); + $armBodyResults[] = $armResult; $armScope = $armResult->getScope(); $armBodyScopes[] = $armScope; $hasYield = $hasYield || $armResult->hasYield(); @@ -370,6 +373,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $matchArmBody = new MatchExpressionArmBody($matchScope, $arm->body); $armNodes[$i] = new MatchExpressionArm($matchArmBody, [], $arm->getStartLine()); $armResult = $nodeScopeResolver->processExprNode($stmt, $arm->body, $matchScope, $storage, $nodeCallback, ExpressionContext::createTopLevel()); + $armBodyResults[] = $armResult; $matchScope = $armResult->getScope(); $hasYield = $hasYield || $armResult->hasYield(); $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); @@ -424,6 +428,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $nodeCallback, ExpressionContext::createTopLevel(), ); + $armBodyResults[] = $armResult; $armScope = $armResult->getScope(); $armBodyScopes[] = $armScope; $hasYield = $hasYield || $armResult->hasYield(); @@ -477,6 +482,14 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($armBodyResults): Type { + $types = []; + foreach ($armBodyResults as $armBodyResult) { + $types[] = $armBodyResult->getTypeForScope($scope); + } + + return TypeCombinator::union(...$types); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php index 25a1499773..49eb622a69 100644 --- a/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php +++ b/src/Analyser/ExprHandler/NullsafeMethodCallHandler.php @@ -17,6 +17,7 @@ use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Type\NullType; @@ -62,6 +63,8 @@ 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 { + $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); + $nonNullabilityResult = $this->nonNullabilityHelper->ensureShallowNonNullability($scope, $scope, $expr->var); $attributes = array_merge($expr->getAttributes(), ['virtualNullsafeMethodCall' => true]); unset($attributes[ExprPrinter::ATTRIBUTE_CACHE_KEY]); @@ -83,6 +86,20 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($varResult, $exprResult): Type { + $varType = $varResult->getTypeForScope($scope); + if ($varType->isNull()->yes()) { + return new NullType(); + } + if (!TypeCombinator::containsNull($varType)) { + return $exprResult->getTypeForScope($scope); + } + + return TypeCombinator::union( + $exprResult->getTypeForScope($scope), + new NullType(), + ); + }, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php index d919ac180b..3e1cac3e85 100644 --- a/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/NullsafePropertyFetchHandler.php @@ -17,6 +17,7 @@ use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Type\NullType; @@ -62,6 +63,8 @@ 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 { + $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); + $nonNullabilityResult = $this->nonNullabilityHelper->ensureShallowNonNullability($scope, $scope, $expr->var); $attributes = array_merge($expr->getAttributes(), ['virtualNullsafePropertyFetch' => true]); unset($attributes[ExprPrinter::ATTRIBUTE_CACHE_KEY]); @@ -75,6 +78,20 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($varResult, $exprResult): Type { + $varType = $varResult->getTypeForScope($scope); + if ($varType->isNull()->yes()) { + return new NullType(); + } + if (!TypeCombinator::containsNull($varType)) { + return $exprResult->getTypeForScope($scope); + } + + return TypeCombinator::union( + $exprResult->getTypeForScope($scope), + new NullType(), + ); + }, hasYield: $exprResult->hasYield(), isAlwaysTerminating: false, throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PipeHandler.php b/src/Analyser/ExprHandler/PipeHandler.php index 8aa1e3c982..8dd21aa993 100644 --- a/src/Analyser/ExprHandler/PipeHandler.php +++ b/src/Analyser/ExprHandler/PipeHandler.php @@ -90,6 +90,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $callResult->getScope(), + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $callResult->getTypeForScope($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 24c5327ff2..84b4490fb1 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 return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $varResult->getTypeForScope($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 e5e55a07fe..fddb634f40 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 return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $varResult->getTypeForScope($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 0d188dbe26..bf8e9e0446 100644 --- a/src/Analyser/ExprHandler/PreDecHandler.php +++ b/src/Analyser/ExprHandler/PreDecHandler.php @@ -14,6 +14,7 @@ use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\BenevolentUnionType; @@ -101,6 +102,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); + $minusResult = $nodeScopeResolver->processExprNode($stmt, new Minus($expr->var, new Int_(1)), $varResult->getScope(), new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); + $scope = $nodeScopeResolver->processVirtualAssign( $varResult->getScope(), $storage, @@ -113,6 +116,54 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($varResult, $minusResult): Type { + $varType = $varResult->getTypeForScope($scope); + $varScalars = $varType->getConstantScalarValues(); + + if (count($varScalars) > 0) { + $newTypes = []; + foreach ($varScalars as $varValue) { + if ($varValue === '') { + $varValue = -1; + } elseif (is_string($varValue) && !is_numeric($varValue)) { + try { + $varValue = str_decrement($varValue); + } catch (ValueError) { + return new NeverType(); + } + } elseif (is_numeric($varValue)) { + --$varValue; + } + + $newTypes[] = $scope->getTypeFromValue($varValue); + } + return TypeCombinator::union(...$newTypes); + } + + if ($varType->isString()->yes()) { + if ($varType->isLiteralString()->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryLiteralStringType(), + ]); + } + + if ($varType->isNumericString()->yes()) { + return new BenevolentUnionType([ + new IntegerType(), + new FloatType(), + ]); + } + + return new BenevolentUnionType([ + new StringType(), + new IntegerType(), + new FloatType(), + ]); + } + + return $minusResult->getTypeForScope($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 50664949fb..e47599c59e 100644 --- a/src/Analyser/ExprHandler/PreIncHandler.php +++ b/src/Analyser/ExprHandler/PreIncHandler.php @@ -14,6 +14,7 @@ use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\BenevolentUnionType; @@ -102,6 +103,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex { $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, $storage, $nodeCallback, $context->enterDeep()); + $plusResult = $nodeScopeResolver->processExprNode($stmt, new Plus($expr->var, new Int_(1)), $varResult->getScope(), new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); + $scope = $nodeScopeResolver->processVirtualAssign( $varResult->getScope(), $storage, @@ -114,6 +117,54 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($varResult, $plusResult): Type { + $varType = $varResult->getTypeForScope($scope); + $varScalars = $varType->getConstantScalarValues(); + + if (count($varScalars) > 0) { + $newTypes = []; + foreach ($varScalars as $varValue) { + if ($varValue === '') { + $varValue = '1'; + } elseif (is_string($varValue) && !is_numeric($varValue)) { + try { + $varValue = str_increment($varValue); + } catch (ValueError) { + return new NeverType(); + } + } elseif (!is_bool($varValue)) { + ++$varValue; + } + + $newTypes[] = $scope->getTypeFromValue($varValue); + } + return TypeCombinator::union(...$newTypes); + } + + if ($varType->isString()->yes()) { + if ($varType->isLiteralString()->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryLiteralStringType(), + ]); + } + + if ($varType->isNumericString()->yes()) { + return new BenevolentUnionType([ + new IntegerType(), + new FloatType(), + ]); + } + + return new BenevolentUnionType([ + new StringType(), + new IntegerType(), + new FloatType(), + ]); + } + + return $plusResult->getTypeForScope($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 9c46b2cf18..4c801a18a7 100644 --- a/src/Analyser/ExprHandler/PrintHandler.php +++ b/src/Analyser/ExprHandler/PrintHandler.php @@ -49,6 +49,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new ConstantIntegerType(1), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PropertyFetchHandler.php b/src/Analyser/ExprHandler/PropertyFetchHandler.php index 4eb5048b74..ae98ac4359 100644 --- a/src/Analyser/ExprHandler/PropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/PropertyFetchHandler.php @@ -82,6 +82,31 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($varResult): Type { + if ($expr->name instanceof Identifier) { + $holderType = $varResult->getTypeForScope($scope); + + if ($scope->nativeTypesPromoted) { + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNodeWithTypes($expr, $scope, $holderType, null); + if ($propertyReflection === null) { + return new ErrorType(); + } + + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); + } + + return $propertyReflection->getNativeType(); + } + + $returnType = $this->propertyFetchType($scope, $holderType, $expr->name->name, $expr); + + return $returnType ?? new ErrorType(); + } + + // TODO: handle dynamic property names + return new MixedType(); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ScalarHandler.php b/src/Analyser/ExprHandler/ScalarHandler.php index e5367c0177..87752665ea 100644 --- a/src/Analyser/ExprHandler/ScalarHandler.php +++ b/src/Analyser/ExprHandler/ScalarHandler.php @@ -42,6 +42,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: fn (Expr $expr, MutatingScope $scope) => $this->initializerExprTypeResolver->getType($expr, InitializerExprContext::fromScope($scope)), hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php index 3f3f2f2dd7..e006c821d4 100644 --- a/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php +++ b/src/Analyser/ExprHandler/StaticPropertyFetchHandler.php @@ -61,6 +61,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex ), ]; $isAlwaysTerminating = false; + $classResult = null; if ($expr->class instanceof Expr) { $classResult = $nodeScopeResolver->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $classResult->hasYield(); @@ -81,6 +82,34 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($classResult): Type { + if ($expr->name instanceof VarLikeIdentifier) { + if ($expr->class instanceof Name) { + $holderType = $scope->resolveTypeByName($expr->class); + } else { + $holderType = TypeCombinator::removeNull($classResult->getTypeForScope($scope))->getObjectTypeOrClassStringObjectType(); + } + + if ($scope->nativeTypesPromoted) { + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNodeWithTypes($expr, $scope, $holderType, null); + if ($propertyReflection === null) { + return new ErrorType(); + } + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); + } + + return $propertyReflection->getNativeType(); + } + + $fetchType = $this->propertyFetchType($scope, $holderType, $expr->name->toString(), $expr); + + return $fetchType ?? new ErrorType(); + } + + // TODO: handle dynamic property names + return new MixedType(); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/TernaryHandler.php b/src/Analyser/ExprHandler/TernaryHandler.php index f08e7d03bf..14c0fa8ba1 100644 --- a/src/Analyser/ExprHandler/TernaryHandler.php +++ b/src/Analyser/ExprHandler/TernaryHandler.php @@ -80,19 +80,17 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = $ternaryCondResult->getImpurePoints(); $ifTrueScope = $ternaryCondResult->getTruthyScope(); $ifFalseScope = $ternaryCondResult->getFalseyScope(); - $ifTrueType = null; - if ($expr->if === null) { $elseResult = $nodeScopeResolver->processExprNode($stmt, $expr->else, $ifFalseScope, $storage, $nodeCallback, $context); $throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $elseResult->getImpurePoints()); $ifFalseScope = $elseResult->getScope(); + $ifResult = null; } else { $ifResult = $nodeScopeResolver->processExprNode($stmt, $expr->if, $ifTrueScope, $storage, $nodeCallback, $context); $throwPoints = array_merge($throwPoints, $ifResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $ifResult->getImpurePoints()); $ifTrueScope = $ifResult->getScope(); - $ifTrueType = $ifTrueScope->getType($expr->if); $elseResult = $nodeScopeResolver->processExprNode($stmt, $expr->else, $ifFalseScope, $storage, $nodeCallback, $context); $throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints()); @@ -100,28 +98,55 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $ifFalseScope = $elseResult->getScope(); } - $condType = $scope->getType($expr->cond); - if ($condType->isTrue()->yes()) { + $ifTrueType = $ifResult !== null ? $ifResult->getType() : null; + $ifFalseType = $elseResult->getType(); + + if ($ternaryCondResult->getType()->toBoolean()->isTrue()->yes()) { $finalScope = $ifTrueScope; - } elseif ($condType->isFalse()->yes()) { + } elseif ($ternaryCondResult->getType()->toBoolean()->isFalse()->yes()) { $finalScope = $ifFalseScope; } else { if ($ifTrueType instanceof NeverType && $ifTrueType->isExplicit()) { $finalScope = $ifFalseScope; + } elseif ($ifFalseType instanceof NeverType && $ifFalseType->isExplicit()) { + $finalScope = $ifTrueScope; } else { - $ifFalseType = $ifFalseScope->getType($expr->else); - - if ($ifFalseType instanceof NeverType && $ifFalseType->isExplicit()) { - $finalScope = $ifTrueScope; - } else { - $finalScope = $ifTrueScope->mergeWith($ifFalseScope); - } + $finalScope = $ifTrueScope->mergeWith($ifFalseScope); } } return $this->expressionResultFactory->create( $expr, $finalScope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($expr, $ternaryCondResult, $ifResult, $elseResult): Type { + $booleanCondType = $ternaryCondResult->getTypeForScope($scope)->toBoolean(); + + if ($expr->if === null) { + if ($booleanCondType->isTrue()->yes()) { + return $ternaryCondResult->getTypeForScope($scope); + } + if ($booleanCondType->isFalse()->yes()) { + return $elseResult->getTypeForScope($scope); + } + + return TypeCombinator::union( + TypeCombinator::removeFalsey($ternaryCondResult->getTypeForScope($scope)), + $elseResult->getTypeForScope($scope), + ); + } + + if ($booleanCondType->isTrue()->yes()) { + return $ifResult->getTypeForScope($scope); + } + if ($booleanCondType->isFalse()->yes()) { + return $elseResult->getTypeForScope($scope); + } + + return TypeCombinator::union( + $ifResult->getTypeForScope($scope), + $elseResult->getTypeForScope($scope), + ); + }, hasYield: $ternaryCondResult->hasYield(), isAlwaysTerminating: $ternaryCondResult->isAlwaysTerminating(), throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ThrowHandler.php b/src/Analyser/ExprHandler/ThrowHandler.php index 3a031658e6..f24bd56cc8 100644 --- a/src/Analyser/ExprHandler/ThrowHandler.php +++ b/src/Analyser/ExprHandler/ThrowHandler.php @@ -43,6 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new NonAcceptingNeverType(), hasYield: false, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), 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 e84ce7833b..af7ba0beea 100644 --- a/src/Analyser/ExprHandler/UnaryMinusHandler.php +++ b/src/Analyser/ExprHandler/UnaryMinusHandler.php @@ -12,6 +12,7 @@ use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\Type; @@ -42,6 +43,13 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $exprResult->getScope(), + typeCallback: fn (Expr $uninteresting, MutatingScope $scope) => $this->initializerExprTypeResolver->getUnaryMinusType($expr->expr, static function (Expr $e) use ($expr, $exprResult, $nodeScopeResolver, $stmt, $scope): Type { + if ($e === $expr->expr) { + return $exprResult->getTypeForScope($scope); + } + + return $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($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 10b5a78e34..0c78e0f0d6 100644 --- a/src/Analyser/ExprHandler/UnaryPlusHandler.php +++ b/src/Analyser/ExprHandler/UnaryPlusHandler.php @@ -40,6 +40,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $exprResult->getScope(), + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $exprResult->getTypeForScope($scope)->toNumber(), hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/VariableHandler.php b/src/Analyser/ExprHandler/VariableHandler.php index eff0cd1705..4980d53455 100644 --- a/src/Analyser/ExprHandler/VariableHandler.php +++ b/src/Analyser/ExprHandler/VariableHandler.php @@ -81,6 +81,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); @@ -96,12 +97,25 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, - $hasYield, - $isAlwaysTerminating, - $throwPoints, - $impurePoints, - static fn (): MutatingScope => $scope->filterByTruthyValue($expr), - static fn (): MutatingScope => $scope->filterByFalseyValue($expr), + typeCallback: static function (Expr $expr, MutatingScope $scope): Type { + if (is_string($expr->name)) { + if ($scope->hasVariableType($expr->name)->no()) { + return new ErrorType(); + } + + return $scope->getVariableType($expr->name); + } + + // TODO: handle dynamic variable names with constant strings + // needs a different approach for filterByTruthyValue + return new MixedType(); + }, + 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/Virtual/AlwaysRememberedExprHandler.php b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php index 82800a2f7b..4311782142 100644 --- a/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/AlwaysRememberedExprHandler.php @@ -50,6 +50,7 @@ public function processExpr( return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $scope->nativeTypesPromoted ? $expr->getNativeExprType() : $expr->getExprType(), 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 be33159213..030dbabc32 100644 --- a/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/Virtual/ExistingArrayDimFetchHandler.php @@ -35,12 +35,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 { - // 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 + $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->getVar(), $scope, $storage, $nodeCallback, $context->enterDeep()); + $dimResult = $nodeScopeResolver->processExprNode($stmt, $expr->getDim(), $varResult->getScope(), $storage, $nodeCallback, $context->enterDeep()); return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $varResult->getTypeForScope($scope)->getOffsetValueType($dimResult->getTypeForScope($scope)), hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php index 1292065303..f06be0a70d 100644 --- a/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/FunctionCallableNodeHandler.php @@ -52,6 +52,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new MixedType(), hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php index f63fcab64e..14d1333d7b 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php @@ -35,12 +35,12 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { - // 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 + $innerResult = $nodeScopeResolver->processExprNode($stmt, $expr->getExpr(), $scope, $storage, $nodeCallback, $context->enterDeep()); return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $scope->getIterableKeyType($innerResult->getTypeForScope($scope)), hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php index c76a428c60..466b975b95 100644 --- a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php @@ -35,12 +35,12 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { - // 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 + $innerResult = $nodeScopeResolver->processExprNode($stmt, $expr->getExpr(), $scope, $storage, $nodeCallback, $context->enterDeep()); return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $scope->getIterableValueType($innerResult->getTypeForScope($scope)), hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php index efe6e5775c..26266c0d5e 100644 --- a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php @@ -35,12 +35,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 { - // 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 + $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->getVar(), $scope, $storage, $nodeCallback, $context->enterDeep()); + $dimResult = $nodeScopeResolver->processExprNode($stmt, $expr->getDim(), $varResult->getScope(), $storage, $nodeCallback, $context->enterDeep()); return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $varResult->getTypeForScope($scope)->getOffsetValueType($dimResult->getTypeForScope($scope)), hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php index 2d07d3a30e..c6e87e5571 100644 --- a/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/InstantiationCallableNodeHandler.php @@ -52,6 +52,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new MixedType(), hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php index 097d2d435d..2049693c04 100644 --- a/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/MethodCallableNodeHandler.php @@ -55,6 +55,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new MixedType(), hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php index 63dd023689..513d748f42 100644 --- a/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/NativeTypeExprHandler.php @@ -41,6 +41,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $scope->nativeTypesPromoted ? $expr->getNativeType() : $expr->getPhpDocType(), hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php index 7c6691badc..c23c6fa266 100644 --- a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser\ExprHandler\Virtual; use PhpParser\Node\Expr; +use PhpParser\Node\Name; use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\ExpressionResult; @@ -38,12 +39,34 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { - // 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 + $propertyFetch = $expr->getPropertyFetch(); + if ($propertyFetch instanceof Expr\PropertyFetch) { + $holderResult = $nodeScopeResolver->processExprNode($stmt, $propertyFetch->var, $scope, $storage, $nodeCallback, $context->enterDeep()); + } else { + $holderResult = $propertyFetch->class instanceof Expr + ? $nodeScopeResolver->processExprNode($stmt, $propertyFetch->class, $scope, $storage, $nodeCallback, $context->enterDeep()) + : null; + } return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($propertyFetch, $holderResult): Type { + if ($holderResult !== null) { + $holderType = $holderResult->getTypeForScope($scope); + } elseif ($propertyFetch instanceof Expr\StaticPropertyFetch && $propertyFetch->class instanceof Name) { + $holderType = $scope->resolveTypeByName($propertyFetch->class); + } else { + return new ErrorType(); + } + + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNodeWithTypes($propertyFetch, $scope, $holderType, null); + if ($propertyReflection === null) { + return new ErrorType(); + } + + return $propertyReflection->getReadableType(); + }, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php index 2fe953546e..13cee9bd4e 100644 --- a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php @@ -37,12 +37,31 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { - // 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 + $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->getVar(), $scope, $storage, $nodeCallback, $context->enterDeep()); + $dimResult = $nodeScopeResolver->processExprNode($stmt, $expr->getDim(), $varResult->getScope(), $storage, $nodeCallback, $context->enterDeep()); + $valueResult = $nodeScopeResolver->processExprNode($stmt, $expr->getValue(), $dimResult->getScope(), $storage, $nodeCallback, $context->enterDeep()); + + $propertyFetchResult = $expr->getVar() instanceof OriginalPropertyTypeExpr + ? $nodeScopeResolver->processExprNode($stmt, $expr->getVar()->getPropertyFetch(), $scope, $storage, $nodeCallback, $context->enterDeep()) + : null; return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($varResult, $dimResult, $valueResult, $propertyFetchResult): Type { + $varType = $varResult->getTypeForScope($scope); + if ($propertyFetchResult !== null) { + $currentPropertyType = $propertyFetchResult->getTypeForScope($scope); + if ($varType instanceof UnionType) { + $varType = $varType->filterTypes(static fn (Type $innerType) => !$innerType->isSuperTypeOf($currentPropertyType)->no()); + } + } + + return $varType->setExistingOffsetValueType( + $dimResult->getTypeForScope($scope), + $valueResult->getTypeForScope($scope), + ); + }, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php index 4bfe1970e1..6319ab5058 100644 --- a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php @@ -37,12 +37,31 @@ public function supports(Expr $expr): bool public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult { - // 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 + $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->getVar(), $scope, $storage, $nodeCallback, $context->enterDeep()); + $dimResult = $expr->getDim() !== null ? $nodeScopeResolver->processExprNode($stmt, $expr->getDim(), $varResult->getScope(), $storage, $nodeCallback, $context->enterDeep()) : null; + $valueResult = $nodeScopeResolver->processExprNode($stmt, $expr->getValue(), ($dimResult ?? $varResult)->getScope(), $storage, $nodeCallback, $context->enterDeep()); + + $propertyFetchResult = $expr->getVar() instanceof OriginalPropertyTypeExpr + ? $nodeScopeResolver->processExprNode($stmt, $expr->getVar()->getPropertyFetch(), $scope, $storage, $nodeCallback, $context->enterDeep()) + : null; return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($varResult, $dimResult, $valueResult, $propertyFetchResult): Type { + $varType = $varResult->getTypeForScope($scope); + if ($propertyFetchResult !== null) { + $currentPropertyType = $propertyFetchResult->getTypeForScope($scope); + if ($varType instanceof UnionType) { + $varType = $varType->filterTypes(static fn (Type $innerType) => !$innerType->isSuperTypeOf($currentPropertyType)->no()); + } + } + + return $varType->setOffsetValueType( + $dimResult !== null ? $dimResult->getTypeForScope($scope) : null, + $valueResult->getTypeForScope($scope), + ); + }, hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php index c0788bd7fa..053f79c03d 100644 --- a/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php +++ b/src/Analyser/ExprHandler/Virtual/StaticMethodCallableNodeHandler.php @@ -61,6 +61,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new MixedType(), hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php index 506a5e1bef..1fffc8a08c 100644 --- a/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/TypeExprHandler.php @@ -41,6 +41,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => $expr->getExprType(), hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php index b98b5e3c7b..e3ab407450 100644 --- a/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/UnsetOffsetExprHandler.php @@ -35,12 +35,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 { - // 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 + $varResult = $nodeScopeResolver->processExprNode($stmt, $expr->getVar(), $scope, $storage, $nodeCallback, $context->enterDeep()); + $dimResult = $nodeScopeResolver->processExprNode($stmt, $expr->getDim(), $varResult->getScope(), $storage, $nodeCallback, $context->enterDeep()); return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $varResult->getTypeForScope($scope)->unsetOffset($dimResult->getTypeForScope($scope)), hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/YieldFromHandler.php b/src/Analyser/ExprHandler/YieldFromHandler.php index 0085588a40..16e3caf013 100644 --- a/src/Analyser/ExprHandler/YieldFromHandler.php +++ b/src/Analyser/ExprHandler/YieldFromHandler.php @@ -58,6 +58,14 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($exprResult): Type { + $generatorReturnType = $exprResult->getTypeForScope($scope)->getTemplateType(Generator::class, 'TReturn'); + if ($generatorReturnType instanceof ErrorType) { + return new MixedType(); + } + + return $generatorReturnType; + }, 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 c2159c200e..6f66412142 100644 --- a/src/Analyser/ExprHandler/YieldHandler.php +++ b/src/Analyser/ExprHandler/YieldHandler.php @@ -88,6 +88,20 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $uninteresting, MutatingScope $scope): Type { + $functionReflection = $scope->getFunction(); + if ($functionReflection === null) { + return new MixedType(); + } + + $returnType = $functionReflection->getReturnType(); + $generatorSendType = $returnType->getTemplateType(Generator::class, 'TSend'); + if ($generatorSendType instanceof ErrorType) { + return new MixedType(); + } + + return $generatorSendType; + }, hasYield: true, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index 4295d25c12..7710858f5b 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -3,12 +3,23 @@ namespace PHPStan\Analyser; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\Match_; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\Variable; use PHPStan\DependencyInjection\GenerateFactory; +use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypeUtils; #[GenerateFactory(interface: ExpressionResultFactory::class)] final class ExpressionResult { + /** @var callable(Expr, MutatingScope): Type */ + private $typeCallback; + /** @var (callable(): MutatingScope)|null */ private $truthyScopeCallback; @@ -19,27 +30,53 @@ final class ExpressionResult private ?MutatingScope $falseyScope = null; + private ?Type $cachedType = null; + + private ?Type $cachedNativeType = null; + + private ?Type $cachedKeepVoidType = null; + /** * @param InternalThrowPoint[] $throwPoints * @param ImpurePoint[] $impurePoints + * @param callable(MutatingScope): Type $typeCallback * @param (callable(): MutatingScope)|null $truthyScopeCallback * @param (callable(): MutatingScope)|null $falseyScopeCallback */ public function __construct( + private ExpressionTypeResolverExtensionRegistryProvider $expressionTypeResolverExtensionRegistryProvider, private Expr $expr, private MutatingScope $scope, private bool $hasYield, private bool $isAlwaysTerminating, private array $throwPoints, private array $impurePoints, + callable $typeCallback, ?callable $truthyScopeCallback = null, ?callable $falseyScopeCallback = null, ) { + $this->typeCallback = $typeCallback; $this->truthyScopeCallback = $truthyScopeCallback; $this->falseyScopeCallback = $falseyScopeCallback; } + private function withExpr(Expr $expr): self + { + return new self( + $this->expressionTypeResolverExtensionRegistryProvider, + $expr, + $this->scope, + $this->hasYield, + $this->isAlwaysTerminating, + $this->throwPoints, + $this->impurePoints, + $this->typeCallback, + $this->truthyScopeCallback, + $this->falseyScopeCallback, + ); + } + public function getScope(): MutatingScope { return $this->scope; @@ -101,4 +138,97 @@ public function isAlwaysTerminating(): bool return $this->isAlwaysTerminating; } + /** + * `ExpressionResult::getType()` is a replacement for `MutatingScope::getType(Expr)` + * for use inside `ExprHandler::processExpr()` implementations. + */ + public function getType(): Type + { + if ($this->cachedType !== null) { + return $this->cachedType; + } + + return $this->cachedType = TypeUtils::resolveLateResolvableTypes($this->getTypeByScope($this->scope)); + } + + /** + * `ExpressionResult::getTypeForScope(Scope)` is used + * instead of `$scope->getType(Expr)` inside typeCallback ExpressionResultFactory argument. + */ + public function getTypeForScope(MutatingScope $scope): Type + { + if ($scope->nativeTypesPromoted) { + return $this->getNativeType(); + } + + return $this->getType(); + } + + /** + * `ExpressionResult::getNativeType()` is a replacement for `MutatingScope::getNativeType(Expr)` + * for use inside `ExprHandler::processExpr()` implementations. + */ + public function getNativeType(): Type + { + if ($this->cachedNativeType !== null) { + return $this->cachedNativeType; + } + + return $this->cachedNativeType = TypeUtils::resolveLateResolvableTypes($this->getTypeByScope($this->scope->doNotTreatPhpDocTypesAsCertain())); + } + + public function getKeepVoidType(): Type + { + if ($this->cachedKeepVoidType !== null) { + return $this->cachedKeepVoidType; + } + + if ( + !$this->expr instanceof Match_ + && ( + ( + !$this->expr instanceof FuncCall + && !$this->expr instanceof MethodCall + && !$this->expr instanceof Expr\NullsafeMethodCall + && !$this->expr instanceof Expr\StaticCall + ) || $this->expr->isFirstClassCallable() + ) + ) { + return $this->getType(); + } + + $originalType = $this->getType(); + if (!TypeCombinator::containsNull($originalType)) { + return $this->cachedKeepVoidType = $originalType; + } + + $clonedExpr = clone $this->expr; + $clonedExpr->setAttribute(MutatingScope::KEEP_VOID_ATTRIBUTE_NAME, true); + + return $this->cachedKeepVoidType = $this->withExpr($clonedExpr)->getType(); + } + + private function getTypeByScope(MutatingScope $scope): Type + { + foreach ($this->expressionTypeResolverExtensionRegistryProvider->getRegistry()->getExtensions() as $extension) { + $type = $extension->getType($this->expr, $scope); + if ($type !== null) { + return $type; + } + } + + if ( + !$this->expr instanceof Variable + && !$this->expr instanceof Expr\Closure + && !$this->expr instanceof Expr\ArrowFunction + && $scope->hasExpressionType($this->expr)->yes() + ) { + $exprString = $scope->getNodeKey($this->expr); + return $scope->expressionTypes[$exprString]->getType(); + } + + $typeCallback = $this->typeCallback; + return $typeCallback($this->expr, $scope); + } + } diff --git a/src/Analyser/ExpressionResultFactory.php b/src/Analyser/ExpressionResultFactory.php index c3d1905d77..06a951639a 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 { @@ -10,6 +11,7 @@ interface ExpressionResultFactory /** * @param InternalThrowPoint[] $throwPoints * @param ImpurePoint[] $impurePoints + * @param callable(Expr, MutatingScope): Type $typeCallback * @param (callable(): MutatingScope)|null $truthyScopeCallback * @param (callable(): MutatingScope)|null $falseyScopeCallback */ @@ -20,6 +22,7 @@ public function create( bool $isAlwaysTerminating, array $throwPoints, array $impurePoints, + callable $typeCallback, ?callable $truthyScopeCallback = null, ?callable $falseyScopeCallback = null, ): ExpressionResult; diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index e60242af43..7d64300f99 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1176,7 +1176,7 @@ public function getKeepVoidType(Expr $node): Type return $this->getType($clonedNode); } - public function doNotTreatPhpDocTypesAsCertain(): Scope + public function doNotTreatPhpDocTypesAsCertain(): self { return $this->promoteNativeTypes(); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 05c05f2888..27e855d48e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2510,12 +2510,21 @@ public function processExprNode( if ($expr instanceof List_) { // only in assign and foreach, processed elsewhere - return $this->expressionResultFactory->create($expr, $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []); + return $this->expressionResultFactory->create( + $expr, + $scope, + typeCallback: static fn () => new MixedType(), + hasYield: false, + isAlwaysTerminating: false, + throwPoints: [], + impurePoints: [], + ); } return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn () => new MixedType(), hasYield: false, isAlwaysTerminating: false, throwPoints: [], @@ -2888,7 +2897,15 @@ 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($expr, $scope, false, $exprResult->isAlwaysTerminating(), $exprResult->getThrowPoints(), $exprResult->getImpurePoints()); + return $this->expressionResultFactory->create( + $expr, + $scope, + typeCallback: static fn (Expr $unassigned, MutatingScope $scope) => $scope->getType($expr), // todo + hasYield: false, + isAlwaysTerminating: $exprResult->isAlwaysTerminating(), + throwPoints: $exprResult->getThrowPoints(), + impurePoints: $exprResult->getImpurePoints(), + ); } /** @@ -3530,7 +3547,15 @@ public function processArgs( } // not storing this, it's scope after processing all args - return $this->expressionResultFactory->create($callLike, $scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); + return $this->expressionResultFactory->create( + $callLike, + $scope, + typeCallback: static fn () => new MixedType(), + hasYield: $hasYield, + isAlwaysTerminating: $isAlwaysTerminating, + throwPoints: $throwPoints, + impurePoints: $impurePoints, + ); } /** @@ -3667,7 +3692,15 @@ public function processVirtualAssign(MutatingScope $scope, ExpressionResultStora $assignedExpr, new VirtualAssignNodeCallback($nodeCallback), ExpressionContext::createDeep(), - fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create($assignedExpr, $scope, hasYield: false, isAlwaysTerminating: false, throwPoints: [], impurePoints: []), + fn (MutatingScope $scope): ExpressionResult => $this->expressionResultFactory->create( + $assignedExpr, + $scope, + typeCallback: static fn () => new MixedType(), + hasYield: false, + isAlwaysTerminating: false, + throwPoints: [], + impurePoints: [], + ), false, ); } diff --git a/src/Analyser/RicherScopeGetTypeHelper.php b/src/Analyser/RicherScopeGetTypeHelper.php index 132c187580..32ed489654 100644 --- a/src/Analyser/RicherScopeGetTypeHelper.php +++ b/src/Analyser/RicherScopeGetTypeHelper.php @@ -10,6 +10,7 @@ use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Type; use PHPStan\Type\TypeResult; use function is_string; @@ -28,6 +29,14 @@ public function __construct( * @return TypeResult */ public function getIdenticalResult(Scope $scope, Identical $expr): TypeResult + { + return $this->getIdenticalResultWithTypes($scope, $expr, $scope->getType($expr->left), $scope->getType($expr->right)); + } + + /** + * @return TypeResult + */ + public function getIdenticalResultWithTypes(Scope $scope, Identical $expr, Type $leftType, Type $rightType): TypeResult { if ( $expr->left instanceof Variable @@ -39,9 +48,6 @@ public function getIdenticalResult(Scope $scope, Identical $expr): TypeResult return new TypeResult(new ConstantBooleanType(true), []); } - $leftType = $scope->getType($expr->left); - $rightType = $scope->getType($expr->right); - if ( ( $expr->left instanceof Node\Expr\PropertyFetch @@ -80,7 +86,15 @@ public function getIdenticalResult(Scope $scope, Identical $expr): TypeResult */ public function getNotIdenticalResult(Scope $scope, Node\Expr\BinaryOp\NotIdentical $expr): TypeResult { - $identicalResult = $this->getIdenticalResult($scope, new Identical($expr->left, $expr->right)); + return $this->getNotIdenticalResultWithTypes($scope, $expr, $scope->getType($expr->left), $scope->getType($expr->right)); + } + + /** + * @return TypeResult + */ + public function getNotIdenticalResultWithTypes(Scope $scope, Node\Expr\BinaryOp\NotIdentical $expr, Type $leftType, Type $rightType): TypeResult + { + $identicalResult = $this->getIdenticalResultWithTypes($scope, new Identical($expr->left, $expr->right), $leftType, $rightType); $identicalType = $identicalResult->type; if ($identicalType instanceof ConstantBooleanType) { return new TypeResult(new ConstantBooleanType(!$identicalType->getValue()), $identicalResult->reasons); diff --git a/src/Rules/Properties/PropertyReflectionFinder.php b/src/Rules/Properties/PropertyReflectionFinder.php index b25682687b..498dd82305 100644 --- a/src/Rules/Properties/PropertyReflectionFinder.php +++ b/src/Rules/Properties/PropertyReflectionFinder.php @@ -90,14 +90,39 @@ public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?F { if ($propertyFetch instanceof Node\Expr\PropertyFetch) { $propertyHolderType = $scope->getType($propertyFetch->var); + $nameType = $propertyFetch->name instanceof Expr ? $scope->getType($propertyFetch->name) : null; + + return $this->findPropertyReflectionFromNodeWithTypes($propertyFetch, $scope, $propertyHolderType, $nameType); + } + + if ($propertyFetch->class instanceof Node\Name) { + $propertyHolderType = $scope->resolveTypeByName($propertyFetch->class); + } else { + $propertyHolderType = $scope->getType($propertyFetch->class); + } + + $nameType = $propertyFetch->name instanceof Expr ? $scope->getType($propertyFetch->name) : null; + + return $this->findPropertyReflectionFromNodeWithTypes($propertyFetch, $scope, $propertyHolderType, $nameType); + } + + /** + * @param Node\Expr\PropertyFetch|Node\Expr\StaticPropertyFetch $propertyFetch + */ + public function findPropertyReflectionFromNodeWithTypes($propertyFetch, Scope $scope, Type $holderType, ?Type $nameType): ?FoundPropertyReflection + { + if ($propertyFetch instanceof Node\Expr\PropertyFetch) { if ($propertyFetch->name instanceof Node\Identifier) { - return $this->findInstancePropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); + return $this->findInstancePropertyReflection($holderType, $propertyFetch->name->name, $scope); + } + + if ($nameType === null) { + return null; } - $nameType = $scope->getType($propertyFetch->name); $nameTypeConstantStrings = $nameType->getConstantStrings(); if (count($nameTypeConstantStrings) === 1) { - return $this->findInstancePropertyReflection($propertyHolderType, $nameTypeConstantStrings[0]->getValue(), $scope); + return $this->findInstancePropertyReflection($holderType, $nameTypeConstantStrings[0]->getValue(), $scope); } return null; @@ -107,13 +132,7 @@ public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?F return null; } - if ($propertyFetch->class instanceof Node\Name) { - $propertyHolderType = $scope->resolveTypeByName($propertyFetch->class); - } else { - $propertyHolderType = $scope->getType($propertyFetch->class); - } - - return $this->findStaticPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); + return $this->findStaticPropertyReflection($holderType, $propertyFetch->name->name, $scope); } private function findInstancePropertyReflection(Type $propertyHolderType, string $propertyName, Scope $scope): ?FoundPropertyReflection From e94f8fdb8ccf1c2706213ec990171b4c80793ccb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Mar 2026 18:55:18 +0100 Subject: [PATCH 04/11] Use ExpressionResult::getType() and getNativeType() instead of `$scope` wherever possible --- .../ExprHandler/ArrayDimFetchHandler.php | 2 +- src/Analyser/ExprHandler/AssignOpHandler.php | 2 +- src/Analyser/ExprHandler/BinaryOpHandler.php | 2 +- .../ExprHandler/CastStringHandler.php | 2 +- src/Analyser/ExprHandler/CoalesceHandler.php | 2 +- src/Analyser/ExprHandler/MatchHandler.php | 4 +- .../ExprHandler/MethodCallHandler.php | 2 +- src/Analyser/ExprHandler/ThrowHandler.php | 2 +- src/Analyser/NodeScopeResolver.php | 75 +++++-------------- 9 files changed, 27 insertions(+), 66 deletions(-) diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index f4f70e4018..89b256af2b 100644 --- a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php @@ -110,7 +110,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $context->enterDeep(), ); - $varType = $scope->getType($expr->var); + $varType = $varResult->getType(); if (!$varType->isArray()->yes() && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) { $throwPoints = array_merge($throwPoints, $offsetGetResult->getThrowPoints()); } diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index 6520dfe91c..12558912a4 100644 --- a/src/Analyser/ExprHandler/AssignOpHandler.php +++ b/src/Analyser/ExprHandler/AssignOpHandler.php @@ -89,7 +89,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $throwPoints = $assignResult->getThrowPoints(); if ( ($expr instanceof Expr\AssignOp\Div || $expr instanceof Expr\AssignOp\Mod) && - !$scope->getType($expr->expr)->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no() + !$assignResult->getType()->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no() ) { $throwPoints[] = InternalThrowPoint::createExplicit($scope, new ObjectType(DivisionByZeroError::class), $expr, false); } diff --git a/src/Analyser/ExprHandler/BinaryOpHandler.php b/src/Analyser/ExprHandler/BinaryOpHandler.php index 24e257140e..f49db02e7e 100644 --- a/src/Analyser/ExprHandler/BinaryOpHandler.php +++ b/src/Analyser/ExprHandler/BinaryOpHandler.php @@ -67,7 +67,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $throwPoints = array_merge($leftResult->getThrowPoints(), $rightResult->getThrowPoints()); if ( ($expr instanceof BinaryOp\Div || $expr instanceof BinaryOp\Mod) && - !$leftResult->getScope()->getType($expr->right)->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no() + !$rightResult->getType()->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no() ) { $throwPoints[] = InternalThrowPoint::createExplicit($leftResult->getScope(), new ObjectType(DivisionByZeroError::class), $expr, false); } diff --git a/src/Analyser/ExprHandler/CastStringHandler.php b/src/Analyser/ExprHandler/CastStringHandler.php index c48a44444c..c36101b795 100644 --- a/src/Analyser/ExprHandler/CastStringHandler.php +++ b/src/Analyser/ExprHandler/CastStringHandler.php @@ -43,7 +43,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); $impurePoints = $exprResult->getImpurePoints(); - $exprType = $scope->getType($expr->expr); + $exprType = $exprResult->getType(); $toStringMethod = $scope->getMethodReflection($exprType, '__toString'); if ($toStringMethod !== null) { if (!$toStringMethod->hasSideEffects()->no()) { diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index 4eaa2b8aa2..e94a120ff6 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -77,7 +77,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $rightScope = $scope->filterByFalseyValue($expr); $rightResult = $nodeScopeResolver->processExprNode($stmt, $expr->right, $rightScope, $storage, $nodeCallback, $context->enterDeep()); - $rightExprType = $scope->getType($expr->right); + $rightExprType = $rightResult->getType(); if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { $scope = $scope->filterByTruthyValue(new Expr\Isset_([$expr->left])); } else { diff --git a/src/Analyser/ExprHandler/MatchHandler.php b/src/Analyser/ExprHandler/MatchHandler.php index d62ee00a2b..e8f46a337a 100644 --- a/src/Analyser/ExprHandler/MatchHandler.php +++ b/src/Analyser/ExprHandler/MatchHandler.php @@ -186,9 +186,9 @@ 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 { $deepContext = $context->enterDeep(); - $condType = $scope->getType($expr->cond); - $condNativeType = $scope->getNativeType($expr->cond); $condResult = $nodeScopeResolver->processExprNode($stmt, $expr->cond, $scope, $storage, $nodeCallback, $deepContext); + $condType = $condResult->getType(); + $condNativeType = $condResult->getNativeType(); $scope = $condResult->getScope(); $hasYield = $condResult->hasYield(); $throwPoints = $condResult->getThrowPoints(); diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index bc9dbde758..2498652b4d 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -100,7 +100,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } $parametersAcceptor = null; $methodReflection = null; - $calledOnType = $scope->getType($expr->var); + $calledOnType = $varResult->getType(); if ($expr->name instanceof Identifier) { $methodName = $expr->name->name; $methodReflection = $scope->getMethodReflection($calledOnType, $methodName); diff --git a/src/Analyser/ExprHandler/ThrowHandler.php b/src/Analyser/ExprHandler/ThrowHandler.php index f24bd56cc8..d250391cb7 100644 --- a/src/Analyser/ExprHandler/ThrowHandler.php +++ b/src/Analyser/ExprHandler/ThrowHandler.php @@ -46,7 +46,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex typeCallback: static fn () => new NonAcceptingNeverType(), hasYield: false, isAlwaysTerminating: $exprResult->isAlwaysTerminating(), - throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createExplicit($scope, $scope->getType($expr->expr), $expr, false)]), + throwPoints: array_merge($exprResult->getThrowPoints(), [InternalThrowPoint::createExplicit($scope, $exprResult->getType(), $expr, false)]), impurePoints: $exprResult->getImpurePoints(), ); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 27e855d48e..b8e04e924c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -781,7 +781,7 @@ public function processStmtNode( && $scope->getFunction() instanceof PhpMethodFromParserNodeReflection && $scope->getFunction()->getDeclaringClass()->hasConstructor() && $scope->getFunction()->getDeclaringClass()->getConstructor()->getName() === $scope->getFunction()->getName() - && TypeUtils::findThisType($scope->getType($node->getPropertyFetch()->var)) !== null + && TypeUtils::findThisType($this->processExprNode(new Node\Stmt\Expression($node->getPropertyFetch()->var), $node->getPropertyFetch()->var, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getType()) !== null ) { return; } @@ -912,7 +912,6 @@ public function processStmtNode( if ($stmt->expr instanceof Expr\Throw_) { $scope = $stmtScope; } - $earlyTerminationExpr = $this->findEarlyTerminatingExpr($stmt->expr, $scope); $hasAssign = false; $currentScope = $scope; $result = $this->processExprNode($stmt, $stmt->expr, $scope, $storage, static function (Node $node, Scope $scope) use ($nodeCallback, $currentScope, &$hasAssign): void { @@ -925,6 +924,7 @@ public function processStmtNode( } $nodeCallback($node, $scope); }, ExpressionContext::createTopLevel()); + $earlyTerminationExpr = $this->findEarlyTerminatingExpr($stmt->expr, $result); $throwPoints = array_filter($result->getThrowPoints(), static fn ($throwPoint) => $throwPoint->isExplicit()); if ( count($result->getImpurePoints()) === 0 @@ -1095,9 +1095,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(); @@ -1131,8 +1131,8 @@ public function processStmtNode( $condScope = $scope; foreach ($stmt->elseifs as $elseif) { $this->callNodeCallback($nodeCallback, $elseif, $scope, $storage); - $elseIfConditionType = ($this->treatPhpDocTypesAsCertain ? $condScope->getType($elseif->cond) : $scope->getNativeType($elseif->cond))->toBoolean(); $condResult = $this->processExprNode($stmt, $elseif->cond, $condScope, $storage, $nodeCallback, ExpressionContext::createDeep()); + $elseIfConditionType = ($this->treatPhpDocTypesAsCertain ? $condResult->getType() : $condResult->getNativeType())->toBoolean(); $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $condResult->getImpurePoints()); $condScope = $condResult->getScope(); @@ -1298,7 +1298,7 @@ public function processStmtNode( $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); } - $exprType = $scope->getType($stmt->expr); + $exprType = $condResult->getType(); $hasExpr = $scope->hasExpressionType($stmt->expr); if ( count($breakExitPoints) === 0 @@ -1336,7 +1336,7 @@ public function processStmtNode( return new ArrayType($type->getKeyType(), $arrayDimFetchLoopType); }); - $newExprNativeType = TypeTraverser::map($scope->getNativeType($stmt->expr), static function (Type $type, callable $traverse) use ($arrayDimFetchLoopNativeType): Type { + $newExprNativeType = TypeTraverser::map($condResult->getNativeType(), static function (Type $type, callable $traverse) use ($arrayDimFetchLoopNativeType): Type { if ($type instanceof UnionType || $type instanceof IntersectionType) { return $traverse($type); } @@ -1387,7 +1387,7 @@ public function processStmtNode( $throwPoints = array_merge($throwPoints, $finalScopeResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $finalScopeResult->getImpurePoints()); } - if (!(new ObjectType(Traversable::class))->isSuperTypeOf($scope->getType($stmt->expr))->no()) { + if (!(new ObjectType(Traversable::class))->isSuperTypeOf($condResult->getType())->no()) { $throwPoints[] = InternalThrowPoint::createImplicit($scope, $stmt->expr); } if ($context->isTopLevel() && $stmt->byRef) { @@ -1406,7 +1406,7 @@ public function processStmtNode( $originalStorage = $storage; $storage = $originalStorage->duplicate(); $condResult = $this->processExprNode($stmt, $stmt->cond, $scope, $storage, new NoopNodeCallback(), ExpressionContext::createDeep()); - $beforeCondBooleanType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean(); + $beforeCondBooleanType = ($this->treatPhpDocTypesAsCertain ? $condResult->getType() : $condResult->getNativeType())->toBoolean(); $condScope = $condResult->getFalseyScope(); if (!$context->isTopLevel() && $beforeCondBooleanType->isFalse()->yes()) { if (!$this->polluteScopeWithLoopInitialAssignments) { @@ -2051,7 +2051,7 @@ public function processStmtNode( $throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $exprResult->getImpurePoints()); if ($var instanceof ArrayDimFetch && $var->dim !== null) { - $varType = $scope->getType($var->var); + $varType = $this->processExprNode($stmt, $var->var, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getType(); if (!$varType->isArray()->yes() && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) { $throwPoints = array_merge($throwPoints, $this->processExprNode( $stmt, @@ -2197,8 +2197,8 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch } $scope = $scope->assignExpression( new Expr\ClassConstFetch(new Name\FullyQualified($scope->getClassReflection()->getName()), $const->name), - $scope->getType($const->value), - $scope->getNativeType($const->value), + $constResult->getType(), + $constResult->getNativeType(), ); } } elseif ($stmt instanceof Node\Stmt\EnumCase) { @@ -2417,50 +2417,10 @@ private function lookForExpressionCallback(MutatingScope $scope, Expr $expr, Clo return $scope; } - private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr + // TODO: move $earlyTerminatingMethodCalls/$earlyTerminatingFunctionCalls config into MethodCallHandler/FuncCallHandler + private function findEarlyTerminatingExpr(Expr $expr, ExpressionResult $result): ?Expr { - if (($expr instanceof MethodCall || $expr instanceof Expr\StaticCall) && $expr->name instanceof Node\Identifier) { - if (array_key_exists($expr->name->toLowerString(), $this->earlyTerminatingMethodNames)) { - if ($expr instanceof MethodCall) { - $methodCalledOnType = $scope->getType($expr->var); - } else { - if ($expr->class instanceof Name) { - $methodCalledOnType = $scope->resolveTypeByName($expr->class); - } else { - $methodCalledOnType = $scope->getType($expr->class); - } - } - - foreach ($methodCalledOnType->getObjectClassNames() as $referencedClass) { - if (!$this->reflectionProvider->hasClass($referencedClass)) { - continue; - } - - $classReflection = $this->reflectionProvider->getClass($referencedClass); - foreach (array_merge([$referencedClass], $classReflection->getParentClassesNames(), $classReflection->getNativeReflection()->getInterfaceNames()) as $className) { - if (!isset($this->earlyTerminatingMethodCalls[$className])) { - continue; - } - - if (in_array((string) $expr->name, $this->earlyTerminatingMethodCalls[$className], true)) { - return $expr; - } - } - } - } - } - - if ($expr instanceof FuncCall && $expr->name instanceof Name) { - if (in_array((string) $expr->name, $this->earlyTerminatingFunctionCalls, true)) { - return $expr; - } - } - - if ($expr instanceof Expr\Exit_ || $expr instanceof Expr\Throw_) { - return $expr; - } - - $exprType = $scope->getType($expr); + $exprType = $result->getType(); if ($exprType instanceof NeverType && $exprType->isExplicit()) { return $expr; } @@ -2689,7 +2649,8 @@ public function processClosureNode( $inAssignRightSideVariableName === $use->var->name && $inAssignRightSideExpr !== null ) { - $inAssignRightSideType = $scope->getType($inAssignRightSideExpr); + $inAssignRightSideResult = $this->processExprNode(new Node\Stmt\Expression($inAssignRightSideExpr), $inAssignRightSideExpr, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); + $inAssignRightSideType = $inAssignRightSideResult->getType(); if ($inAssignRightSideType instanceof ClosureType) { $variableType = $inAssignRightSideType; } else { @@ -2700,7 +2661,7 @@ public function processClosureNode( $variableType = TypeCombinator::union($scope->getVariableType($inAssignRightSideVariableName), $inAssignRightSideType); } } - $inAssignRightSideNativeType = $scope->getNativeType($inAssignRightSideExpr); + $inAssignRightSideNativeType = $inAssignRightSideResult->getNativeType(); if ($inAssignRightSideNativeType instanceof ClosureType) { $variableNativeType = $inAssignRightSideNativeType; } else { From 57ee9ca6e3e05964b2bc084dda4e72075d65cec1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Mar 2026 10:57:32 +0100 Subject: [PATCH 05/11] FuncCallHandler and MethodCallHandler --- src/Analyser/ExprHandler/FuncCallHandler.php | 104 +++++++++++++++++- .../ExprHandler/MethodCallHandler.php | 29 +++++ 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index 15189544ea..ee106ae857 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -104,8 +104,10 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $throwPoints = []; $impurePoints = []; $isAlwaysTerminating = false; + $nameResult = null; if ($expr->name instanceof Expr) { - $nameType = $scope->getType($expr->name); + $nameResult = $nodeScopeResolver->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); + $nameType = $nameResult->getType(); if (!$nameType->isCallable()->no()) { $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, @@ -115,7 +117,6 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex ); } - $nameResult = $nodeScopeResolver->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); $scope = $nameResult->getScope(); $throwPoints = $nameResult->getThrowPoints(); $impurePoints = $nameResult->getImpurePoints(); @@ -475,6 +476,105 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($nameResult, $nodeScopeResolver, $stmt): Type { + if ($expr->name instanceof Expr) { + $calledOnType = $nameResult->getTypeForScope($scope); + if ($calledOnType->isCallable()->no()) { + return new ErrorType(); + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $expr->getArgs(), + $calledOnType->getCallableParametersAcceptors($scope), + null, + ); + + $functionName = null; + if ($expr->name instanceof String_) { + /** @var non-empty-string $name */ + $name = $expr->name->value; + $functionName = new Name($name); + } elseif ( + $expr->name instanceof FuncCall + && $expr->name->name instanceof Name + && $expr->name->isFirstClassCallable() + ) { + $functionName = $expr->name->name; + } + + $normalizedNode = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr); + if ($normalizedNode !== null && $functionName !== null && $this->reflectionProvider->hasFunction($functionName, $scope)) { + $functionReflection = $this->reflectionProvider->getFunction($functionName, $scope); + $resolvedType = $this->getDynamicFunctionReturnType($scope, $normalizedNode, $functionReflection); + if ($resolvedType !== null) { + return $resolvedType; + } + } + + return $parametersAcceptor->getReturnType(); + } + + if (!$this->reflectionProvider->hasFunction($expr->name, $scope)) { + return new ErrorType(); + } + + $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); + if ($scope->nativeTypesPromoted) { + return ParametersAcceptorSelector::combineAcceptors($functionReflection->getVariants())->getNativeReturnType(); + } + + if ($functionReflection->getName() === 'call_user_func') { + $result = ArgumentsNormalizer::reorderCallUserFuncArguments($expr, $scope); + if ($result !== null) { + [, $innerFuncCall] = $result; + + return $nodeScopeResolver->processExprNode($stmt, $innerFuncCall, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + } + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $expr->getArgs(), + $functionReflection->getVariants(), + $functionReflection->getNamedArgumentsVariants(), + ); + $normalizedNode = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr); + if ($normalizedNode !== null) { + if ($functionReflection->getName() === 'clone' && count($normalizedNode->getArgs()) > 0) { + $cloneResult = $nodeScopeResolver->processExprNode($stmt, new Expr\Clone_($normalizedNode->getArgs()[0]->value), $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); + $cloneType = $cloneResult->getTypeForScope($scope); + if (count($normalizedNode->getArgs()) === 2) { + $propertiesResult = $nodeScopeResolver->processExprNode($stmt, $normalizedNode->getArgs()[1]->value, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep()); + $propertiesType = $propertiesResult->getTypeForScope($scope); + if ($propertiesType->isConstantArray()->yes()) { + $constantArrays = $propertiesType->getConstantArrays(); + if (count($constantArrays) === 1) { + $accessories = []; + foreach ($constantArrays[0]->getKeyTypes() as $keyType) { + $constantKeyTypes = $keyType->getConstantScalarValues(); + if (count($constantKeyTypes) !== 1) { + return $cloneType; + } + $accessories[] = new HasPropertyType((string) $constantKeyTypes[0]); + } + if (count($accessories) > 0 && count($accessories) <= 16) { + return TypeCombinator::intersect($cloneType, ...$accessories); + } + } + } + } + + return $cloneType; + } + $resolvedType = $this->getDynamicFunctionReturnType($scope, $normalizedNode, $functionReflection); + if ($resolvedType !== null) { + return $resolvedType; + } + } + + return VoidToNullTypeTransformer::transform($parametersAcceptor->getReturnType(), $expr); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index 2498652b4d..9df0c03a2c 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -196,6 +196,35 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $result = $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($varResult): Type { + if ($expr->name instanceof Identifier) { + $varType = $varResult->getTypeForScope($scope); + + if ($scope->nativeTypesPromoted) { + $methodReflection = $scope->getMethodReflection( + $varType, + $expr->name->name, + ); + if ($methodReflection === null) { + return new ErrorType(); + } + + return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); + } + + $returnType = $this->methodCallReturnTypeHelper->methodCallReturnType( + $scope, + $varType, + $expr->name->name, + $expr, + ); + + return $returnType ?? new ErrorType(); + } + + // TODO: handle dynamic method names + return new MixedType(); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, From 20ccc34e4b7a0a23fd67980ca875d84aa81c504f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Mar 2026 11:03:50 +0100 Subject: [PATCH 06/11] NewHandler and StaticCallHandler --- src/Analyser/ExprHandler/NewHandler.php | 12 ++++++ .../ExprHandler/StaticCallHandler.php | 40 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index b42bd6d455..58fb43a791 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -88,6 +88,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = []; $isAlwaysTerminating = false; $normalizedExpr = $expr; + $classResult = null; if ($expr->class instanceof Name) { $className = $scope->resolveName($expr->class); @@ -207,6 +208,17 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($classResult): Type { + if ($expr->class instanceof Name) { + return $this->exactInstantiation($scope, $expr, $expr->class); + } + if ($expr->class instanceof Node\Stmt\Class_) { + $anonymousClassReflection = $this->reflectionProvider->getAnonymousClassReflection($expr->class, $scope); + return new ObjectType($anonymousClassReflection->getName()); + } + + return $classResult->getTypeForScope($scope)->getObjectTypeOrClassStringObjectType(); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index 0d5e112d96..3cdf7d9f02 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -79,6 +79,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $throwPoints = []; $impurePoints = []; $isAlwaysTerminating = false; + $classResult = null; if ($expr->class instanceof Expr) { $classResult = $nodeScopeResolver->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $classResult->hasYield(); @@ -262,6 +263,45 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($classResult): Type { + if ($expr->name instanceof Identifier) { + if ($expr->class instanceof Name) { + $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($scope, $expr->class, $expr->name); + } elseif ($classResult !== null) { + if ($scope->nativeTypesPromoted) { + $staticMethodCalledOnType = $classResult->getTypeForScope($scope); + } else { + $staticMethodCalledOnType = TypeCombinator::removeNull($classResult->getTypeForScope($scope))->getObjectTypeOrClassStringObjectType(); + } + } else { + return new ErrorType(); + } + + if ($scope->nativeTypesPromoted) { + $methodReflection = $scope->getMethodReflection( + $staticMethodCalledOnType, + $expr->name->name, + ); + if ($methodReflection === null) { + return new ErrorType(); + } + + return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); + } + + $callType = $this->methodCallReturnTypeHelper->methodCallReturnType( + $scope, + $staticMethodCalledOnType, + $expr->name->toString(), + $expr, + ); + + return $callType ?? new ErrorType(); + } + + // TODO: handle dynamic method names + return new MixedType(); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, From 35ed979b7458b7f5453f2f00d1f04c3635130fd5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Mar 2026 11:16:26 +0100 Subject: [PATCH 07/11] earlyTerminating calls refactored to always infer expr type as NeverType --- src/Analyser/ExprHandler/FuncCallHandler.php | 5 +++ .../ExprHandler/MethodCallHandler.php | 33 +++++++++++++++++ .../ExprHandler/StaticCallHandler.php | 36 +++++++++++++++++++ src/Analyser/NodeScopeResolver.php | 1 - 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index ee106ae857..3224ea8a7c 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -88,6 +88,8 @@ public function __construct( private bool $implicitThrows, #[AutowiredParameter] private bool $rememberPossiblyImpureFunctionValues, + #[AutowiredParameter] + private array $earlyTerminatingFunctionCalls, ) { } @@ -165,6 +167,9 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $returnType = $parametersAcceptor->getReturnType(); $isAlwaysTerminating = $isAlwaysTerminating || $returnType instanceof NeverType && $returnType->isExplicit(); } + if (!$isAlwaysTerminating && $expr->name instanceof Name && in_array((string) $expr->name, $this->earlyTerminatingFunctionCalls, true)) { + $isAlwaysTerminating = true; + } if ( $normalizedExpr->name instanceof Name diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index 9df0c03a2c..b24a371a17 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -26,6 +26,7 @@ use PHPStan\Node\Expr\PossiblyImpureCallExpr; use PHPStan\Node\InvalidateExprNode; use PHPStan\Reflection\Callables\SimpleImpurePoint; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptor; @@ -65,10 +66,23 @@ public function __construct( private bool $implicitThrows, #[AutowiredParameter] private bool $rememberPossiblyImpureFunctionValues, + #[AutowiredParameter] + private array $earlyTerminatingMethodCalls, + private ReflectionProvider $reflectionProvider, ) { + $earlyTerminatingMethodNames = []; + foreach ($this->earlyTerminatingMethodCalls as $methodNames) { + foreach ($methodNames as $methodName) { + $earlyTerminatingMethodNames[strtolower($methodName)] = true; + } + } + $this->earlyTerminatingMethodNames = $earlyTerminatingMethodNames; } + /** @var array */ + private array $earlyTerminatingMethodNames; + public function supports(Expr $expr): bool { return $expr instanceof MethodCall && !$expr->isFirstClassCallable(); @@ -144,6 +158,25 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $returnType = $parametersAcceptor->getReturnType(); $isAlwaysTerminating = $returnType instanceof NeverType && $returnType->isExplicit(); } + if (!$isAlwaysTerminating && $expr->name instanceof Identifier && array_key_exists($expr->name->toLowerString(), $this->earlyTerminatingMethodNames)) { + foreach ($calledOnType->getObjectClassNames() as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + foreach (array_merge([$referencedClass], $classReflection->getParentClassesNames(), $classReflection->getNativeReflection()->getInterfaceNames()) as $className) { + if (!isset($this->earlyTerminatingMethodCalls[$className])) { + continue; + } + + if (in_array($expr->name->name, $this->earlyTerminatingMethodCalls[$className], true)) { + $isAlwaysTerminating = true; + break 2; + } + } + } + } $argsResult = $nodeScopeResolver->processArgs( $stmt, diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index 3cdf7d9f02..1b88390c61 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -31,6 +31,7 @@ use PHPStan\Reflection\Callables\SimpleImpurePoint; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\ErrorType; use PHPStan\Type\MixedType; @@ -64,10 +65,23 @@ public function __construct( private bool $implicitThrows, #[AutowiredParameter] private bool $rememberPossiblyImpureFunctionValues, + #[AutowiredParameter] + private array $earlyTerminatingMethodCalls, + private ReflectionProvider $reflectionProvider, ) { + $earlyTerminatingMethodNames = []; + foreach ($this->earlyTerminatingMethodCalls as $methodNames) { + foreach ($methodNames as $methodName) { + $earlyTerminatingMethodNames[strtolower($methodName)] = true; + } + } + $this->earlyTerminatingMethodNames = $earlyTerminatingMethodNames; } + /** @var array */ + private array $earlyTerminatingMethodNames; + public function supports(Expr $expr): bool { return $expr instanceof StaticCall && !$expr->isFirstClassCallable(); @@ -202,6 +216,28 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $returnType = $parametersAcceptor->getReturnType(); $isAlwaysTerminating = $returnType instanceof NeverType && $returnType->isExplicit(); } + if (!$isAlwaysTerminating && $expr->name instanceof Identifier && array_key_exists($expr->name->toLowerString(), $this->earlyTerminatingMethodNames)) { + $staticCalledOnType = $expr->class instanceof Name ? $scope->resolveTypeByName($expr->class) : ($classResult !== null ? $classResult->getType() : null); + if ($staticCalledOnType !== null) { + foreach ($staticCalledOnType->getObjectClassNames() as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + foreach (array_merge([$referencedClass], $classReflection->getParentClassesNames(), $classReflection->getNativeReflection()->getInterfaceNames()) as $className) { + if (!isset($this->earlyTerminatingMethodCalls[$className])) { + continue; + } + + if (in_array($expr->name->name, $this->earlyTerminatingMethodCalls[$className], true)) { + $isAlwaysTerminating = true; + break 2; + } + } + } + } + } $argsResult = $nodeScopeResolver->processArgs($stmt, $methodReflection, null, $parametersAcceptor, $normalizedExpr, $scope, $storage, $nodeCallback, $context, $closureBindScope); $scope = $argsResult->getScope(); $scopeFunction = $scope->getFunction(); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index b8e04e924c..2c0eca2853 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2417,7 +2417,6 @@ private function lookForExpressionCallback(MutatingScope $scope, Expr $expr, Clo return $scope; } - // TODO: move $earlyTerminatingMethodCalls/$earlyTerminatingFunctionCalls config into MethodCallHandler/FuncCallHandler private function findEarlyTerminatingExpr(Expr $expr, ExpressionResult $result): ?Expr { $exprType = $result->getType(); From 7fcae9c9cef3e6cb9d409d19c91c57e61e7a34d2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Mar 2026 11:41:27 +0100 Subject: [PATCH 08/11] Refactored issetCheck in MutatingScope to be usable inside typeCallback --- src/Analyser/MutatingScope.php | 70 ++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 7d64300f99..42c149b920 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -994,6 +994,15 @@ private function resolveType(string $exprString, Expr $node): Type public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = null): ?bool { // mirrored in PHPStan\Rules\IssetCheck + return $this->issetCheckWithResolver($expr, $typeCallback, fn (Expr $expr): Type => $this->getType($expr), $result); + } + + /** + * @param callable(Type): ?bool $typeCallback + * @param callable(Expr): Type $typeResolver + */ + public function issetCheckWithResolver(Expr $expr, callable $typeCallback, callable $typeResolver, ?bool $result = null): ?bool + { if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { $hasVariable = $this->hasVariableType($expr->name); if ($hasVariable->maybe()) { @@ -1014,41 +1023,46 @@ public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = n return $result; } elseif ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { - $type = $this->getType($expr->var); + $type = $typeResolver($expr->var); if (!$type->isOffsetAccessible()->yes()) { - return $result ?? $this->issetCheckUndefined($expr->var); + return $result ?? $this->issetCheckUndefinedWithResolver($expr->var, $typeResolver); } - $dimType = $this->getType($expr->dim); + $dimType = $typeResolver($expr->dim); $hasOffsetValue = $type->hasOffsetValueType($dimType); if ($hasOffsetValue->no()) { return false; } - // If offset cannot be null, store this error message and see if one of the earlier offsets is. - // E.g. $array['a']['b']['c'] ?? null; is a valid coalesce if a OR b or C might be null. if ($hasOffsetValue->yes()) { $result = $typeCallback($type->getOffsetValueType($dimType)); if ($result !== null) { - return $this->issetCheck($expr->var, $typeCallback, $result); + return $this->issetCheckWithResolver($expr->var, $typeCallback, $typeResolver, $result); } } - // Has offset, it is nullable return null; } elseif ($expr instanceof Node\Expr\PropertyFetch || $expr instanceof Node\Expr\StaticPropertyFetch) { - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this); + if ($expr instanceof Node\Expr\PropertyFetch) { + $holderType = $typeResolver($expr->var); + } elseif ($expr->class instanceof Name) { + $holderType = $this->resolveTypeByName($expr->class); + } else { + $holderType = $typeResolver($expr->class); + } + + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNodeWithTypes($expr, $this, $holderType, null); if ($propertyReflection === null) { if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->issetCheckUndefined($expr->var); + return $this->issetCheckUndefinedWithResolver($expr->var, $typeResolver); } if ($expr->class instanceof Expr) { - return $this->issetCheckUndefined($expr->class); + return $this->issetCheckUndefinedWithResolver($expr->class, $typeResolver); } return null; @@ -1056,11 +1070,11 @@ public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = n if (!$propertyReflection->isNative()) { if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->issetCheckUndefined($expr->var); + return $this->issetCheckUndefinedWithResolver($expr->var, $typeResolver); } if ($expr->class instanceof Expr) { - return $this->issetCheckUndefined($expr->class); + return $this->issetCheckUndefinedWithResolver($expr->class, $typeResolver); } return null; @@ -1069,11 +1083,11 @@ public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = n if ($propertyReflection->hasNativeType() && !$propertyReflection->isVirtual()->yes()) { if (!$this->hasExpressionType($expr)->yes()) { if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->issetCheckUndefined($expr->var); + return $this->issetCheckUndefinedWithResolver($expr->var, $typeResolver); } if ($expr->class instanceof Expr) { - return $this->issetCheckUndefined($expr->class); + return $this->issetCheckUndefinedWithResolver($expr->class, $typeResolver); } return null; @@ -1087,11 +1101,11 @@ public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = n $result = $typeCallback($propertyReflection->getWritableType()); if ($result !== null) { if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->issetCheck($expr->var, $typeCallback, $result); + return $this->issetCheckWithResolver($expr->var, $typeCallback, $typeResolver, $result); } if ($expr->class instanceof Expr) { - return $this->issetCheck($expr->class, $typeCallback, $result); + return $this->issetCheckWithResolver($expr->class, $typeCallback, $typeResolver, $result); } } @@ -1102,10 +1116,13 @@ public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = n return $result; } - return $typeCallback($this->getType($expr)); + return $typeCallback($typeResolver($expr)); } - private function issetCheckUndefined(Expr $expr): ?bool + /** + * @param callable(Expr): Type $typeResolver + */ + private function issetCheckUndefinedWithResolver(Expr $expr, callable $typeResolver): ?bool { if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { $hasVariable = $this->hasVariableType($expr->name); @@ -1117,32 +1134,37 @@ private function issetCheckUndefined(Expr $expr): ?bool } if ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { - $type = $this->getType($expr->var); + $type = $typeResolver($expr->var); if (!$type->isOffsetAccessible()->yes()) { - return $this->issetCheckUndefined($expr->var); + return $this->issetCheckUndefinedWithResolver($expr->var, $typeResolver); } - $dimType = $this->getType($expr->dim); + $dimType = $typeResolver($expr->dim); $hasOffsetValue = $type->hasOffsetValueType($dimType); if (!$hasOffsetValue->no()) { - return $this->issetCheckUndefined($expr->var); + return $this->issetCheckUndefinedWithResolver($expr->var, $typeResolver); } return false; } if ($expr instanceof Expr\PropertyFetch) { - return $this->issetCheckUndefined($expr->var); + return $this->issetCheckUndefinedWithResolver($expr->var, $typeResolver); } if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { - return $this->issetCheckUndefined($expr->class); + return $this->issetCheckUndefinedWithResolver($expr->class, $typeResolver); } return null; } + private function issetCheckUndefined(Expr $expr): ?bool + { + return $this->issetCheckUndefinedWithResolver($expr, fn (Expr $expr): Type => $this->getType($expr)); + } + /** @api */ public function getNativeType(Expr $expr): Type { From c99ef33ae2c7d81a3dbc38ef742e97b6a8995e8b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Mar 2026 13:06:20 +0100 Subject: [PATCH 09/11] Use earlyTerminating*Calls in handler resolveType to return NeverType --- src/Analyser/ExprHandler/FuncCallHandler.php | 4 ++++ .../ExprHandler/MethodCallHandler.php | 20 +++++++++++++++++++ .../ExprHandler/StaticCallHandler.php | 20 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index 3224ea8a7c..c06c3e3d7c 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -822,6 +822,10 @@ private function getArraySortDoNotPreserveListFunctionType(Type $type): Type public function resolveType(MutatingScope $scope, Expr $expr): Type { + if ($expr->name instanceof Name && in_array((string) $expr->name, $this->earlyTerminatingFunctionCalls, true)) { + return new NeverType(true); + } + if ($expr->name instanceof Expr) { $calledOnType = $scope->getType($expr->name); if ($calledOnType->isCallable()->no()) { diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index b24a371a17..a1d7196823 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -349,6 +349,26 @@ private function getMethodThrowPoint(MethodReflection $methodReflection, Paramet public function resolveType(MutatingScope $scope, Expr $expr): Type { + if ($expr->name instanceof Identifier && array_key_exists($expr->name->toLowerString(), $this->earlyTerminatingMethodNames)) { + $calledOnType = $scope->getType($expr->var); + foreach ($calledOnType->getObjectClassNames() as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + foreach (array_merge([$referencedClass], $classReflection->getParentClassesNames(), $classReflection->getNativeReflection()->getInterfaceNames()) as $className) { + if (!isset($this->earlyTerminatingMethodCalls[$className])) { + continue; + } + + if (in_array($expr->name->name, $this->earlyTerminatingMethodCalls[$className], true)) { + return new NeverType(true); + } + } + } + } + if ($expr->name instanceof Identifier) { if ($scope->nativeTypesPromoted) { $methodReflection = $scope->getMethodReflection( diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index 1b88390c61..157ccb87dd 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -382,6 +382,26 @@ private function getStaticMethodThrowPoint(MethodReflection $methodReflection, P public function resolveType(MutatingScope $scope, Expr $expr): Type { + if ($expr->name instanceof Identifier && array_key_exists($expr->name->toLowerString(), $this->earlyTerminatingMethodNames)) { + $staticCalledOnType = $expr->class instanceof Name ? $scope->resolveTypeByName($expr->class) : $scope->getType($expr->class); + foreach ($staticCalledOnType->getObjectClassNames() as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + foreach (array_merge([$referencedClass], $classReflection->getParentClassesNames(), $classReflection->getNativeReflection()->getInterfaceNames()) as $className) { + if (!isset($this->earlyTerminatingMethodCalls[$className])) { + continue; + } + + if (in_array($expr->name->name, $this->earlyTerminatingMethodCalls[$className], true)) { + return new NeverType(true); + } + } + } + } + if ($expr->name instanceof Identifier) { if ($scope->nativeTypesPromoted) { if ($expr->class instanceof Name) { From fed60a2a77375963b193fde3f52afd15853b2357 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Mar 2026 13:06:58 +0100 Subject: [PATCH 10/11] Implement typeCallback in Coalesce/Empty/Isset handlers --- src/Analyser/ExprHandler/CoalesceHandler.php | 30 ++++++++++++------ src/Analyser/ExprHandler/EmptyHandler.php | 26 ++++++++++++++++ src/Analyser/ExprHandler/IssetHandler.php | 32 +++++++++++++++++++- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/Analyser/ExprHandler/CoalesceHandler.php b/src/Analyser/ExprHandler/CoalesceHandler.php index e94a120ff6..1496d857b2 100644 --- a/src/Analyser/ExprHandler/CoalesceHandler.php +++ b/src/Analyser/ExprHandler/CoalesceHandler.php @@ -13,6 +13,7 @@ use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\NeverType; use PHPStan\Type\Type; @@ -87,22 +88,33 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, - typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($condResult, $rightResult): Type { + typeCallback: static function (Expr $uninteresting, MutatingScope $scope) use ($expr, $condResult, $rightResult, $nodeScopeResolver, $stmt): Type { + $typeResolver = static fn (Expr $e): Type => $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + + $issetResult = $scope->issetCheckWithResolver($expr->left, static function (Type $type): ?bool { + $isNull = $type->isNull(); + if ($isNull->maybe()) { + return null; + } + + return !$isNull->yes(); + }, $typeResolver); + $leftType = $condResult->getTypeForScope($scope); $rightType = $rightResult->getTypeForScope($scope); - if ($leftType->isNull()->yes()) { - return $rightType; + if ($issetResult !== null && $issetResult !== false) { + return TypeCombinator::removeNull($leftType); } - if (!TypeCombinator::containsNull($leftType)) { - return $leftType; + if ($issetResult === null) { + return TypeCombinator::union( + TypeCombinator::removeNull($leftType), + $rightType, + ); } - return TypeCombinator::union( - TypeCombinator::removeNull($leftType), - $rightType, - ); + return $rightType; }, hasYield: $condResult->hasYield() || $rightResult->hasYield(), isAlwaysTerminating: $condResult->isAlwaysTerminating(), diff --git a/src/Analyser/ExprHandler/EmptyHandler.php b/src/Analyser/ExprHandler/EmptyHandler.php index 8af2be1576..e6db0acc07 100644 --- a/src/Analyser/ExprHandler/EmptyHandler.php +++ b/src/Analyser/ExprHandler/EmptyHandler.php @@ -13,6 +13,7 @@ use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -74,6 +75,31 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $expr, MutatingScope $scope) use ($nodeScopeResolver, $stmt): Type { + $typeResolver = static fn (Expr $e): Type => $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + + $result = $scope->issetCheckWithResolver($expr->expr, static function (Type $type): ?bool { + $isNull = $type->isNull(); + $isFalsey = $type->toBoolean()->isFalse(); + if ($isNull->maybe()) { + return null; + } + if ($isFalsey->maybe()) { + return null; + } + + if ($isNull->yes()) { + return $isFalsey->no(); + } + + return !$isFalsey->yes(); + }, $typeResolver); + if ($result === null) { + return new BooleanType(); + } + + return new ConstantBooleanType(!$result); + }, hasYield: $exprResult->hasYield(), isAlwaysTerminating: $exprResult->isAlwaysTerminating(), throwPoints: $exprResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/IssetHandler.php b/src/Analyser/ExprHandler/IssetHandler.php index 83d363ba02..d8b68be655 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.php @@ -96,7 +96,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex continue; } - $varType = $scope->getType($var->var); + $varType = $nodeScopeResolver->processExprNode($stmt, $var->var, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), $context->enterDeep())->getType(); if ($varType->isArray()->yes() || (new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) { continue; } @@ -120,6 +120,36 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static function (Expr $expr, MutatingScope $scope) use ($nodeScopeResolver, $stmt): Type { + $typeResolver = static fn (Expr $e): Type => $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + + $issetResult = true; + foreach ($expr->vars as $var) { + $result = $scope->issetCheckWithResolver($var, static function (Type $type): ?bool { + $isNull = $type->isNull(); + if ($isNull->maybe()) { + return null; + } + + return !$isNull->yes(); + }, $typeResolver); + if ($result !== null) { + if (!$result) { + return new ConstantBooleanType($result); + } + + continue; + } + + $issetResult = $result; + } + + if ($issetResult === null) { + return new BooleanType(); + } + + return new ConstantBooleanType($issetResult); + }, hasYield: $hasYield, isAlwaysTerminating: $isAlwaysTerminating, throwPoints: $throwPoints, From 7fe5e4ec6b3c730db33677f1f0c62bcf9c6c9f6b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Mar 2026 13:21:43 +0100 Subject: [PATCH 11/11] Remaining fixes --- src/Analyser/ExprHandler/AssignOpHandler.php | 68 +++++++++++++++++-- src/Analyser/ExprHandler/ClosureHandler.php | 2 + .../ExprHandler/ConstFetchHandler.php | 1 + .../ExprHandler/MethodCallHandler.php | 1 + src/Analyser/NodeScopeResolver.php | 2 +- 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index 12558912a4..6ab448a8fc 100644 --- a/src/Analyser/ExprHandler/AssignOpHandler.php +++ b/src/Analyser/ExprHandler/AssignOpHandler.php @@ -17,6 +17,7 @@ use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\ShouldNotHappenException; @@ -71,10 +72,11 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto return $this->expressionResultFactory->create( $expr, $exprResult->getScope()->mergeWith($originalScope), - $exprResult->hasYield(), - $exprResult->isAlwaysTerminating(), - $exprResult->getThrowPoints(), - $exprResult->getImpurePoints(), + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $exprResult->getTypeForScope($scope), + hasYield: $exprResult->hasYield(), + isAlwaysTerminating: $exprResult->isAlwaysTerminating(), + throwPoints: $exprResult->getThrowPoints(), + impurePoints: $exprResult->getImpurePoints(), ); } @@ -97,6 +99,64 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: function (Expr $expr, MutatingScope $scope) use ($assignResult, $nodeScopeResolver, $stmt): Type { + if ($expr instanceof Expr\AssignOp\Coalesce) { + // Coalesce assignop type is handled by BinaryOp\Coalesce + return $nodeScopeResolver->processExprNode($stmt, new BinaryOp\Coalesce($expr->var, $expr->expr, $expr->getAttributes()), $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + } + + $varType = $nodeScopeResolver->processExprNode($stmt, $expr->var, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + $exprType = $assignResult->getTypeForScope($scope); + $getType = static function (Expr $e) use ($expr, $varType, $exprType, $scope, $nodeScopeResolver, $stmt): Type { + if ($e === $expr->var) { + return $varType; + } + if ($e === $expr->expr) { + return $exprType; + } + + return $nodeScopeResolver->processExprNode($stmt, $e, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getTypeForScope($scope); + }; + + if ($expr instanceof Expr\AssignOp\Concat) { + return $this->initializerExprTypeResolver->getConcatType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\BitwiseAnd) { + return $this->initializerExprTypeResolver->getBitwiseAndType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\BitwiseOr) { + return $this->initializerExprTypeResolver->getBitwiseOrType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\BitwiseXor) { + return $this->initializerExprTypeResolver->getBitwiseXorType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\Div) { + return $this->initializerExprTypeResolver->getDivType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\Mod) { + return $this->initializerExprTypeResolver->getModType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\Plus) { + return $this->initializerExprTypeResolver->getPlusType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\Minus) { + return $this->initializerExprTypeResolver->getMinusType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\Mul) { + return $this->initializerExprTypeResolver->getMulType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\Pow) { + return $this->initializerExprTypeResolver->getPowType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\ShiftLeft) { + return $this->initializerExprTypeResolver->getShiftLeftType($expr->var, $expr->expr, $getType); + } + if ($expr instanceof Expr\AssignOp\ShiftRight) { + return $this->initializerExprTypeResolver->getShiftRightType($expr->var, $expr->expr, $getType); + } + + throw new ShouldNotHappenException(sprintf('Unhandled %s', get_class($expr))); + }, hasYield: $assignResult->hasYield(), isAlwaysTerminating: $assignResult->isAlwaysTerminating(), throwPoints: $throwPoints, diff --git a/src/Analyser/ExprHandler/ClosureHandler.php b/src/Analyser/ExprHandler/ClosureHandler.php index 7f733802b3..939032d2f1 100644 --- a/src/Analyser/ExprHandler/ClosureHandler.php +++ b/src/Analyser/ExprHandler/ClosureHandler.php @@ -43,6 +43,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + // TODO: replace with proper typeCallback that doesn't use $scope->getType() + typeCallback: fn (Expr $expr, MutatingScope $scope) => $this->closureTypeResolver->getClosureType($scope, $expr), hasYield: false, isAlwaysTerminating: false, throwPoints: [], diff --git a/src/Analyser/ExprHandler/ConstFetchHandler.php b/src/Analyser/ExprHandler/ConstFetchHandler.php index 3154e256c3..b1eac69f53 100644 --- a/src/Analyser/ExprHandler/ConstFetchHandler.php +++ b/src/Analyser/ExprHandler/ConstFetchHandler.php @@ -46,6 +46,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $constName = (string) $expr->name; $loweredConstName = strtolower($constName); + $names = null; if ($loweredConstName === 'true') { $constType = new ConstantBooleanType(true); } elseif ($loweredConstName === 'false') { diff --git a/src/Analyser/ExprHandler/MethodCallHandler.php b/src/Analyser/ExprHandler/MethodCallHandler.php index a1d7196823..32dcc02211 100644 --- a/src/Analyser/ExprHandler/MethodCallHandler.php +++ b/src/Analyser/ExprHandler/MethodCallHandler.php @@ -287,6 +287,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex return $this->expressionResultFactory->create( $expr, $scope, + typeCallback: static fn (Expr $uninteresting, MutatingScope $scope) => $result->getTypeForScope($scope), hasYield: $result->hasYield(), isAlwaysTerminating: $result->isAlwaysTerminating(), throwPoints: $result->getThrowPoints(), diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 2c0eca2853..b69c776e3f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -781,7 +781,7 @@ public function processStmtNode( && $scope->getFunction() instanceof PhpMethodFromParserNodeReflection && $scope->getFunction()->getDeclaringClass()->hasConstructor() && $scope->getFunction()->getDeclaringClass()->getConstructor()->getName() === $scope->getFunction()->getName() - && TypeUtils::findThisType($this->processExprNode(new Node\Stmt\Expression($node->getPropertyFetch()->var), $node->getPropertyFetch()->var, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep())->getType()) !== null + && TypeUtils::findThisType($scope->getType($node->getPropertyFetch()->var)) !== null ) { return; }