From 7c587ab5dac77ae67df52475d472dc832c9762c4 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Fri, 18 Oct 2024 22:04:10 +0200 Subject: [PATCH] Only use last for condition to filter scope --- src/Analyser/NodeScopeResolver.php | 21 ++++++++++++------- .../PHPStan/Analyser/nsrt/for-loop-i-type.php | 8 +++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 969d0be6b4..054ec1a2f9 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1314,13 +1314,18 @@ private function processStmtNode( $bodyScope = $initScope; $isIterableAtLeastOnce = TrinaryLogic::createYes(); + $lastCondExpr = $stmt->cond[count($stmt->cond) - 1] ?? null; foreach ($stmt->cond as $condExpr) { $condResult = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void { }, ExpressionContext::createDeep()); $initScope = $condResult->getScope(); $condResultScope = $condResult->getScope(); - $condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResultScope->getType($condExpr) : $condResultScope->getNativeType($condExpr))->toBoolean(); - $isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue()); + + if ($condExpr === $lastCondExpr) { + $condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResultScope->getType($condExpr) : $condResultScope->getNativeType($condExpr))->toBoolean(); + $isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue()); + } + $hasYield = $hasYield || $condResult->hasYield(); $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $condResult->getImpurePoints()); @@ -1332,8 +1337,8 @@ private function processStmtNode( do { $prevScope = $bodyScope; $bodyScope = $bodyScope->mergeWith($initScope); - foreach ($stmt->cond as $condExpr) { - $bodyScope = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void { + if ($lastCondExpr !== null) { + $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, static function (): void { }, ExpressionContext::createDeep())->getTruthyScope(); } $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { @@ -1363,8 +1368,8 @@ private function processStmtNode( } $bodyScope = $bodyScope->mergeWith($initScope); - foreach ($stmt->cond as $condExpr) { - $bodyScope = $this->processExprNode($stmt, $condExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); + if ($lastCondExpr !== null) { + $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); } $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); @@ -1378,8 +1383,8 @@ private function processStmtNode( $loopScope = $this->processExprNode($stmt, $loopExpr, $loopScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); } $finalScope = $finalScope->generalizeWith($loopScope); - foreach ($stmt->cond as $condExpr) { - $finalScope = $finalScope->filterByFalseyValue($condExpr); + if ($lastCondExpr !== null) { + $finalScope = $finalScope->filterByFalseyValue($lastCondExpr); } foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { diff --git a/tests/PHPStan/Analyser/nsrt/for-loop-i-type.php b/tests/PHPStan/Analyser/nsrt/for-loop-i-type.php index 011d15d6c7..1317b3695c 100644 --- a/tests/PHPStan/Analyser/nsrt/for-loop-i-type.php +++ b/tests/PHPStan/Analyser/nsrt/for-loop-i-type.php @@ -94,4 +94,12 @@ public static function groupCapacities(array $startTimes): array return $capacities; } + + public function lastConditionResult(): void + { + for ($i = 0, $j = 5; $i < 10, $j > 0; $i++, $j--) { + assertType('int<0, max>', $i); // int<0,4> would be more precise, see https://github.com/phpstan/phpstan/issues/11872 + assertType('int<1, 5>', $j); + } + } }