From 0d5ba03d25b60bb369e8e847d7dae98e90ae1f99 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 28 Jul 2026 19:36:30 +0200 Subject: [PATCH] Read already-processed expression types from ExpressionResults in NodeScopeResolver Straightforward conversions where the read targets an expression whose result is in hand: the for-loop last-condition truthiness, the const/class-const declaration values (the scope is exactly the value result's before-scope), and the foreach loop-value narrowing read directly by variable name (an assigned variable, not a processed expression). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019wqGgaD7iqL44t1KgpJS7b --- src/Analyser/NodeScopeResolver.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 82efccb10b..789b4844c6 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1616,7 +1616,10 @@ public function processStmtNode( // the narrowed value-var type in place of the broader dim fetch type so // the loop's final array rewrite below picks up the sharper element type. if ($originalValueExpr !== null && $scopeWithIterableValueType->hasExpressionType($originalValueExpr)->yes()) { - $valueVarType = $scopeWithIterableValueType->getType($stmt->valueVar); + // read the loop value variable's narrowed type directly by name - + // it is an assigned (not processExprNode-processed) variable + // ($originalValueExpr !== null implies a string-named Variable) + $valueVarType = $scopeWithIterableValueType->getVariableType($stmt->valueVar->name); if ($dimFetchType->isSuperTypeOf($valueVarType)->yes()) { $dimFetchType = $valueVarType; } @@ -1629,7 +1632,7 @@ public function processStmtNode( $keyLoopNativeTypes[] = $scopeWithIterableValueType->getType($keyVarExpr); } else { // No key variable: the narrowed value var is the array element type directly. - $dimFetchType = $scopeWithIterableValueType->getType($stmt->valueVar); + $dimFetchType = $scopeWithIterableValueType->getVariableType($stmt->valueVar->name); $dimFetchNativeType = $scopeWithIterableValueType->getNativeType($stmt->valueVar); } $arrayDimFetchLoopTypes[] = $dimFetchType; @@ -1946,7 +1949,7 @@ public function processStmtNode( // only the last condition expression is relevant whether the loop continues // see https://www.php.net/manual/en/control-structures.for.php if ($condExpr === $lastCondExpr) { - $condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResultScope->getType($condExpr) : $condResultScope->getNativeType($condExpr))->toBoolean(); + $condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResult->getType() : $condResult->getNativeType())->toBoolean(); $isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue()); } @@ -2504,7 +2507,7 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch } else { $constantName = new Name\FullyQualified($const->name->toString()); } - $scope = $scope->assignExpression(new ConstFetch($constantName), $scope->getType($const->value), $scope->getNativeType($const->value)); + $scope = $scope->assignExpression(new ConstFetch($constantName), $constResult->getType(), $constResult->getNativeType()); } } elseif ($stmt instanceof Node\Stmt\ClassConst) { $hasYield = false; @@ -2520,8 +2523,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) {