From 19b98b36a241ebbb53e9ffa42441d70da81061b2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Jun 2026 07:27:03 +0200 Subject: [PATCH 1/4] Eliminate the OriginalPropertyTypeExpr virtual node --- src/Analyser/ExprHandler/AssignHandler.php | 24 +++++- .../OriginalPropertyTypeExprHandler.php | 74 ------------------- .../SetExistingOffsetValueTypeExprHandler.php | 11 +-- .../Virtual/SetOffsetValueTypeExprHandler.php | 11 +-- src/Node/Expr/OriginalPropertyTypeExpr.php | 37 ---------- src/Node/Printer/Printer.php | 6 -- 6 files changed, 23 insertions(+), 140 deletions(-) delete mode 100644 src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php delete mode 100644 src/Node/Expr/OriginalPropertyTypeExpr.php diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index bc92f76bbc4..fae2deb1be9 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -42,7 +42,6 @@ use PHPStan\Node\Expr\ExistingArrayDimFetch; use PHPStan\Node\Expr\GetOffsetValueTypeExpr; use PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr; -use PHPStan\Node\Expr\OriginalPropertyTypeExpr; use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr; use PHPStan\Node\Expr\SetOffsetValueTypeExpr; use PHPStan\Node\Expr\TypeExpr; @@ -52,6 +51,7 @@ use PHPStan\Node\VariableAssignNode; use PHPStan\Node\VirtualNode; use PHPStan\Php\PhpVersion; +use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -73,6 +73,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; +use PHPStan\Type\UnionType; use TypeError; use function array_key_last; use function array_merge; @@ -97,6 +98,7 @@ public function __construct( private ExprPrinter $exprPrinter, private MatchHandler $matchHandler, private ExpressionResultFactory $expressionResultFactory, + private PropertyReflectionFinder $propertyReflectionFinder, ) { } @@ -550,7 +552,7 @@ public function processAssignVar( while ($var instanceof ArrayDimFetch) { $varForSetOffsetValue = $var->var; if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { - $varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); + $varForSetOffsetValue = new TypeExpr($this->getOriginalPropertyType($varForSetOffsetValue, $scope)); } if ( @@ -987,7 +989,7 @@ public function processAssignVar( while ($var instanceof ExistingArrayDimFetch) { $varForSetOffsetValue = $var->getVar(); if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { - $varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); + $varForSetOffsetValue = new TypeExpr($this->getOriginalPropertyType($varForSetOffsetValue, $scope)); } $assignedPropertyExpr = new SetExistingOffsetValueTypeExpr( $varForSetOffsetValue, @@ -1645,4 +1647,20 @@ private function isSameVariable(Expr $a, Expr $b): bool return false; } + /** + * Returns the property's readable (declared) type, filtered down to the union + * members that are not disjoint from the currently narrowed property type. + */ + private function getOriginalPropertyType(PropertyFetch|StaticPropertyFetch $propertyFetch, MutatingScope $scope): Type + { + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyFetch, $scope); + $originalPropertyType = $propertyReflection !== null ? $propertyReflection->getReadableType() : new ErrorType(); + if ($originalPropertyType instanceof UnionType) { + $currentPropertyType = $scope->getType($propertyFetch); + $originalPropertyType = $originalPropertyType->filterTypes(static fn (Type $innerType) => !$innerType->isSuperTypeOf($currentPropertyType)->no()); + } + + return $originalPropertyType; + } + } diff --git a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php deleted file mode 100644 index 2621d242cc7..00000000000 --- a/src/Analyser/ExprHandler/Virtual/OriginalPropertyTypeExprHandler.php +++ /dev/null @@ -1,74 +0,0 @@ - - */ -#[AutowiredService] -final class OriginalPropertyTypeExprHandler implements ExprHandler -{ - - public function __construct( - private PropertyReflectionFinder $propertyReflectionFinder, - private ExpressionResultFactory $expressionResultFactory, - ) - { - } - - public function supports(Expr $expr): bool - { - return $expr instanceof OriginalPropertyTypeExpr; - } - - 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 - - return $this->expressionResultFactory->create( - $scope, - beforeScope: $scope, - expr: $expr, - hasYield: false, - isAlwaysTerminating: false, - throwPoints: [], - impurePoints: [], - ); - } - - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr->getPropertyFetch(), $scope); - if ($propertyReflection === null) { - return new ErrorType(); - } - - return $propertyReflection->getReadableType(); - } - - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); - } - -} diff --git a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php index 11487e514f2..31f221a9e32 100644 --- a/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetExistingOffsetValueTypeExprHandler.php @@ -16,10 +16,8 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; -use PHPStan\Node\Expr\OriginalPropertyTypeExpr; use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr; use PHPStan\Type\Type; -use PHPStan\Type\UnionType; /** * @implements ExprHandler @@ -55,14 +53,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex public function resolveType(MutatingScope $scope, Expr $expr): Type { - $varNode = $expr->getVar(); - $varType = $scope->getType($varNode); - if ($varNode instanceof OriginalPropertyTypeExpr) { - $currentPropertyType = $scope->getType($varNode->getPropertyFetch()); - if ($varType instanceof UnionType) { - $varType = $varType->filterTypes(static fn (Type $innerType) => !$innerType->isSuperTypeOf($currentPropertyType)->no()); - } - } + $varType = $scope->getType($expr->getVar()); return $varType->setExistingOffsetValueType( $scope->getType($expr->getDim()), $scope->getType($expr->getValue()), diff --git a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php index c85440094b8..ee9201224ee 100644 --- a/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php +++ b/src/Analyser/ExprHandler/Virtual/SetOffsetValueTypeExprHandler.php @@ -16,10 +16,8 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; -use PHPStan\Node\Expr\OriginalPropertyTypeExpr; use PHPStan\Node\Expr\SetOffsetValueTypeExpr; use PHPStan\Type\Type; -use PHPStan\Type\UnionType; /** * @implements ExprHandler @@ -55,14 +53,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex public function resolveType(MutatingScope $scope, Expr $expr): Type { - $varNode = $expr->getVar(); - $varType = $scope->getType($varNode); - if ($varNode instanceof OriginalPropertyTypeExpr) { - $currentPropertyType = $scope->getType($varNode->getPropertyFetch()); - if ($varType instanceof UnionType) { - $varType = $varType->filterTypes(static fn (Type $innerType) => !$innerType->isSuperTypeOf($currentPropertyType)->no()); - } - } + $varType = $scope->getType($expr->getVar()); return $varType->setOffsetValueType( $expr->getDim() !== null ? $scope->getType($expr->getDim()) : null, $scope->getType($expr->getValue()), diff --git a/src/Node/Expr/OriginalPropertyTypeExpr.php b/src/Node/Expr/OriginalPropertyTypeExpr.php deleted file mode 100644 index d662dbe488f..00000000000 --- a/src/Node/Expr/OriginalPropertyTypeExpr.php +++ /dev/null @@ -1,37 +0,0 @@ -propertyFetch; - } - - #[Override] - public function getType(): string - { - return 'PHPStan_Node_OriginalPropertyTypeExpr'; - } - - /** - * @return string[] - */ - #[Override] - public function getSubNodeNames(): array - { - return []; - } - -} diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index 283ddaa2c19..97a528a0e3e 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -21,7 +21,6 @@ use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\Expr\OriginalForeachKeyExpr; use PHPStan\Node\Expr\OriginalForeachValueExpr; -use PHPStan\Node\Expr\OriginalPropertyTypeExpr; use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr; use PHPStan\Node\Expr\PossiblyImpureCallExpr; use PHPStan\Node\Expr\PropertyInitializationExpr; @@ -142,11 +141,6 @@ protected function pPHPStan_Node_ExistingArrayDimFetch(ExistingArrayDimFetch $ex return sprintf('__phpstanExistingArrayDimFetch(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim())); } - protected function pPHPStan_Node_OriginalPropertyTypeExpr(OriginalPropertyTypeExpr $expr): string // phpcs:ignore - { - return sprintf('__phpstanOriginalPropertyType(%s)', $this->p($expr->getPropertyFetch())); - } - protected function pPHPStan_Node_SetOffsetValueTypeExpr(SetOffsetValueTypeExpr $expr): string // phpcs:ignore { return sprintf('__phpstanSetOffsetValueType(%s, %s, %s)', $this->p($expr->getVar()), $expr->getDim() !== null ? $this->p($expr->getDim()) : 'null', $this->p($expr->getValue())); From 02b9227733d4bca83881a82a07c98b7839c3da25 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Jun 2026 08:08:29 +0200 Subject: [PATCH 2/4] Eliminate the GetOffsetValueTypeExpr virtual node --- src/Analyser/ExprHandler/AssignHandler.php | 3 +- .../Virtual/GetOffsetValueTypeExprHandler.php | 64 ------------------- src/Node/Expr/GetOffsetValueTypeExpr.php | 42 ------------ src/Node/Printer/Printer.php | 6 -- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 4 +- 5 files changed, 3 insertions(+), 116 deletions(-) delete mode 100644 src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php delete mode 100644 src/Node/Expr/GetOffsetValueTypeExpr.php diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index fae2deb1be9..8f3fbeedae2 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -40,7 +40,6 @@ use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Expr\ExistingArrayDimFetch; -use PHPStan\Node\Expr\GetOffsetValueTypeExpr; use PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr; use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr; use PHPStan\Node\Expr\SetOffsetValueTypeExpr; @@ -964,7 +963,7 @@ public function processAssignVar( } else { $dimExpr = $arrayItem->key; } - $getOffsetValueTypeExpr = new GetOffsetValueTypeExpr($assignedExpr, $dimExpr); + $getOffsetValueTypeExpr = new TypeExpr($scope->getType($assignedExpr)->getOffsetValueType($scope->getType($dimExpr))); $result = $this->processAssignVar( $nodeScopeResolver, $scope, diff --git a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php deleted file mode 100644 index ed0b5da45c5..00000000000 --- a/src/Analyser/ExprHandler/Virtual/GetOffsetValueTypeExprHandler.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ -#[AutowiredService] -final class GetOffsetValueTypeExprHandler implements ExprHandler -{ - - public function __construct(private ExpressionResultFactory $expressionResultFactory) - { - } - - public function supports(Expr $expr): bool - { - return $expr instanceof GetOffsetValueTypeExpr; - } - - 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 - - return $this->expressionResultFactory->create( - $scope, - beforeScope: $scope, - expr: $expr, - hasYield: false, - isAlwaysTerminating: false, - throwPoints: [], - impurePoints: [], - ); - } - - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - return $scope->getType($expr->getVar())->getOffsetValueType($scope->getType($expr->getDim())); - } - - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); - } - -} diff --git a/src/Node/Expr/GetOffsetValueTypeExpr.php b/src/Node/Expr/GetOffsetValueTypeExpr.php deleted file mode 100644 index 38226855557..00000000000 --- a/src/Node/Expr/GetOffsetValueTypeExpr.php +++ /dev/null @@ -1,42 +0,0 @@ -var; - } - - public function getDim(): Expr - { - return $this->dim; - } - - #[Override] - public function getType(): string - { - return 'PHPStan_Node_GetOffsetValueTypeExpr'; - } - - /** - * @return string[] - */ - #[Override] - public function getSubNodeNames(): array - { - return []; - } - -} diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index 97a528a0e3e..abed880aa92 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -16,7 +16,6 @@ use PHPStan\Node\Expr\ForeachValueByRefExpr; use PHPStan\Node\Expr\GetIterableKeyTypeExpr; use PHPStan\Node\Expr\GetIterableValueTypeExpr; -use PHPStan\Node\Expr\GetOffsetValueTypeExpr; use PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr; use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\Expr\OriginalForeachKeyExpr; @@ -116,11 +115,6 @@ protected function pPHPStan_Node_NativeTypeExpr(NativeTypeExpr $expr): string // return sprintf('__phpstanNativeType(%s, %s)', $expr->getPhpDocType()->describe(VerbosityLevel::precise()), $expr->getNativeType()->describe(VerbosityLevel::precise())); } - protected function pPHPStan_Node_GetOffsetValueTypeExpr(GetOffsetValueTypeExpr $expr): string // phpcs:ignore - { - return sprintf('__phpstanGetOffsetValueType(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim())); - } - protected function pPHPStan_Node_UnsetOffsetExpr(UnsetOffsetExpr $expr): string // phpcs:ignore { return sprintf('__phpstanUnsetOffset(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim())); diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index 4a6e8421d91..127501aa58c 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -8,7 +8,7 @@ use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; -use PHPStan\Node\Expr\GetOffsetValueTypeExpr; +use PHPStan\Node\Expr\TypeExpr; use PHPStan\PhpDoc\NameScopeAlreadyBeingCreatedException; use PHPStan\PhpDoc\Tag\VarTag; use PHPStan\PhpDoc\TypeNodeResolver; @@ -78,7 +78,7 @@ public function checkVarType(Scope $scope, Node\Expr $var, Node\Expr $expr, arra $dimExpr = $arrayItem->key; } - $itemErrors = $this->checkVarType($scope, $arrayItem->value, new GetOffsetValueTypeExpr($expr, $dimExpr), $varTags, $assignedVariables); + $itemErrors = $this->checkVarType($scope, $arrayItem->value, new TypeExpr($scope->getType($expr)->getOffsetValueType($scope->getType($dimExpr))), $varTags, $assignedVariables); foreach ($itemErrors as $error) { $errors[] = $error; } From 1767d119e51e85f27c255407cc044e4ec24726cb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Jun 2026 08:38:52 +0200 Subject: [PATCH 3/4] Eliminate the GetIterableKeyTypeExpr virtual node --- .../Virtual/GetIterableKeyTypeExprHandler.php | 64 ------------------- src/Analyser/MutatingScope.php | 7 +- src/Analyser/NodeScopeResolver.php | 13 +++- src/Node/Expr/GetIterableKeyTypeExpr.php | 37 ----------- src/Node/Printer/Printer.php | 6 -- src/Rules/Arrays/ArrayUnpackingRule.php | 7 +- .../PhpDoc/WrongVariableNameInVarTagRule.php | 8 ++- 7 files changed, 26 insertions(+), 116 deletions(-) delete mode 100644 src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php delete mode 100644 src/Node/Expr/GetIterableKeyTypeExpr.php diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php deleted file mode 100644 index 127ef939539..00000000000 --- a/src/Analyser/ExprHandler/Virtual/GetIterableKeyTypeExprHandler.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ -#[AutowiredService] -final class GetIterableKeyTypeExprHandler implements ExprHandler -{ - - public function __construct(private ExpressionResultFactory $expressionResultFactory) - { - } - - public function supports(Expr $expr): bool - { - return $expr instanceof GetIterableKeyTypeExpr; - } - - 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 - - return $this->expressionResultFactory->create( - $scope, - beforeScope: $scope, - expr: $expr, - hasYield: false, - isAlwaysTerminating: false, - throwPoints: [], - impurePoints: [], - ); - } - - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - return $scope->getIterableKeyType($scope->getType($expr->getExpr())); - } - - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); - } - -} diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 58c962b05a0..11bea7b52b6 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -28,8 +28,8 @@ use PHPStan\Node\EmitCollectedDataNode; use PHPStan\Node\Expr\AlwaysRememberedExpr; use PHPStan\Node\Expr\CloneReinitializationExpr; -use PHPStan\Node\Expr\GetIterableKeyTypeExpr; use PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr; +use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\Expr\OriginalForeachKeyExpr; use PHPStan\Node\Expr\OriginalForeachValueExpr; use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr; @@ -2560,7 +2560,10 @@ public function enterForeach(self $originalScope, Expr $iteratee, string $valueN $scope = $scope->assignExpression( new IntertwinedVariableByReferenceWithExpr($valueName, $iteratee, new SetExistingOffsetValueTypeExpr( $iteratee, - new GetIterableKeyTypeExpr($iteratee), + new NativeTypeExpr( + $originalScope->getIterableKeyType($iterateeType), + $originalScope->getIterableKeyType($nativeIterateeType), + ), new Variable($valueName), )), $valueType, diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index d7496a53592..0fcebfe64a6 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -77,8 +77,8 @@ use PHPStan\Node\ExecutionEndNode; use PHPStan\Node\Expr\ExistingArrayDimFetch; use PHPStan\Node\Expr\ForeachValueByRefExpr; -use PHPStan\Node\Expr\GetIterableKeyTypeExpr; use PHPStan\Node\Expr\GetIterableValueTypeExpr; +use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\Expr\OriginalForeachKeyExpr; use PHPStan\Node\Expr\OriginalForeachValueExpr; use PHPStan\Node\Expr\PropertyInitializationExpr; @@ -1493,7 +1493,11 @@ public function processStmtNode( $bodyScope = $scope; if ($stmt->keyVar instanceof Variable) { - $this->callNodeCallback($nodeCallback, new VariableAssignNode($stmt->keyVar, new GetIterableKeyTypeExpr($stmt->expr)), $originalScope, $storage); + $keyTypeExpr = new NativeTypeExpr( + $originalScope->getIterableKeyType($originalScope->getType($stmt->expr)), + $originalScope->getIterableKeyType($originalScope->getNativeType($stmt->expr)), + ); + $this->callNodeCallback($nodeCallback, new VariableAssignNode($stmt->keyVar, $keyTypeExpr), $originalScope, $storage); } if ($stmt->valueVar instanceof Variable) { @@ -4513,7 +4517,10 @@ private function enterForeach(MutatingScope $scope, ExpressionResultStorage $sto $storage, $stmt, $stmt->keyVar, - new GetIterableKeyTypeExpr($stmt->expr), + new NativeTypeExpr( + $originalScope->getIterableKeyType($iterateeType), + $originalScope->getIterableKeyType($originalScope->getNativeType($stmt->expr)), + ), $nodeCallback, )->getScope(); $vars = array_merge($vars, $this->getAssignedVariables($stmt->keyVar)); diff --git a/src/Node/Expr/GetIterableKeyTypeExpr.php b/src/Node/Expr/GetIterableKeyTypeExpr.php deleted file mode 100644 index 60731730539..00000000000 --- a/src/Node/Expr/GetIterableKeyTypeExpr.php +++ /dev/null @@ -1,37 +0,0 @@ -expr; - } - - #[Override] - public function getType(): string - { - return 'PHPStan_Node_GetIterableKeyTypeExpr'; - } - - /** - * @return string[] - */ - #[Override] - public function getSubNodeNames(): array - { - return []; - } - -} diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index abed880aa92..cbe086e6593 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -14,7 +14,6 @@ use PHPStan\Node\Expr\CloneReinitializationExpr; use PHPStan\Node\Expr\ExistingArrayDimFetch; use PHPStan\Node\Expr\ForeachValueByRefExpr; -use PHPStan\Node\Expr\GetIterableKeyTypeExpr; use PHPStan\Node\Expr\GetIterableValueTypeExpr; use PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr; use PHPStan\Node\Expr\NativeTypeExpr; @@ -125,11 +124,6 @@ protected function pPHPStan_Node_GetIterableValueTypeExpr(GetIterableValueTypeEx return sprintf('__phpstanGetIterableValueType(%s)', $this->p($expr->getExpr())); } - protected function pPHPStan_Node_GetIterableKeyTypeExpr(GetIterableKeyTypeExpr $expr): string // phpcs:ignore - { - return sprintf('__phpstanGetIterableKeyType(%s)', $this->p($expr->getExpr())); - } - protected function pPHPStan_Node_ExistingArrayDimFetch(ExistingArrayDimFetch $expr): string // phpcs:ignore { return sprintf('__phpstanExistingArrayDimFetch(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim())); diff --git a/src/Rules/Arrays/ArrayUnpackingRule.php b/src/Rules/Arrays/ArrayUnpackingRule.php index 4dc25092a9a..08cb915cad3 100644 --- a/src/Rules/Arrays/ArrayUnpackingRule.php +++ b/src/Rules/Arrays/ArrayUnpackingRule.php @@ -6,7 +6,7 @@ use PhpParser\Node\ArrayItem; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\RegisteredRule; -use PHPStan\Node\Expr\GetIterableKeyTypeExpr; +use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -40,7 +40,10 @@ public function processNode(Node $node, Scope $scope): array $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - new GetIterableKeyTypeExpr($node->value), + new NativeTypeExpr( + $scope->getIterableKeyType($scope->getType($node->value)), + $scope->getIterableKeyType($scope->getNativeType($node->value)), + ), '', static fn (Type $type): bool => $type->isString()->no(), ); diff --git a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php index 6296e12ca6c..66b7ca6ce8f 100644 --- a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php +++ b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php @@ -7,8 +7,8 @@ use PhpParser\Node\Expr; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\RegisteredRule; -use PHPStan\Node\Expr\GetIterableKeyTypeExpr; use PHPStan\Node\Expr\GetIterableValueTypeExpr; +use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\InClassMethodNode; use PHPStan\Node\InClassNode; use PHPStan\Node\InFunctionNode; @@ -271,7 +271,11 @@ private function processForeach(Scope $scope, Node\Expr $iterateeExpr, ?Node\Exp $errors[] = $error; } if ($keyVar !== null) { - foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $keyVar, new GetIterableKeyTypeExpr($iterateeExpr), $varTags, $variableNames) as $error) { + $keyTypeExpr = new NativeTypeExpr( + $scope->getIterableKeyType($scope->getScopeType($iterateeExpr)), + $scope->getIterableKeyType($scope->getScopeNativeType($iterateeExpr)), + ); + foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $keyVar, $keyTypeExpr, $varTags, $variableNames) as $error) { $errors[] = $error; } } From f14a0105520549954cde439d08a4500c8c673d53 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Jun 2026 09:10:26 +0200 Subject: [PATCH 4/4] Eliminate the GetIterableValueTypeExpr virtual node --- .../GetIterableValueTypeExprHandler.php | 64 ------------------- src/Analyser/NodeScopeResolver.php | 17 +++-- src/Node/Expr/GetIterableValueTypeExpr.php | 37 ----------- src/Node/Printer/Printer.php | 6 -- .../PhpDoc/WrongVariableNameInVarTagRule.php | 7 +- 5 files changed, 18 insertions(+), 113 deletions(-) delete mode 100644 src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php delete mode 100644 src/Node/Expr/GetIterableValueTypeExpr.php diff --git a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php b/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php deleted file mode 100644 index 56b7b9be00e..00000000000 --- a/src/Analyser/ExprHandler/Virtual/GetIterableValueTypeExprHandler.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ -#[AutowiredService] -final class GetIterableValueTypeExprHandler implements ExprHandler -{ - - public function __construct(private ExpressionResultFactory $expressionResultFactory) - { - } - - public function supports(Expr $expr): bool - { - return $expr instanceof GetIterableValueTypeExpr; - } - - 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 - - return $this->expressionResultFactory->create( - $scope, - beforeScope: $scope, - expr: $expr, - hasYield: false, - isAlwaysTerminating: false, - throwPoints: [], - impurePoints: [], - ); - } - - public function resolveType(MutatingScope $scope, Expr $expr): Type - { - return $scope->getIterableValueType($scope->getType($expr->getExpr())); - } - - public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes - { - return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); - } - -} diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 0fcebfe64a6..696104b2014 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -77,7 +77,6 @@ use PHPStan\Node\ExecutionEndNode; use PHPStan\Node\Expr\ExistingArrayDimFetch; use PHPStan\Node\Expr\ForeachValueByRefExpr; -use PHPStan\Node\Expr\GetIterableValueTypeExpr; use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\Expr\OriginalForeachKeyExpr; use PHPStan\Node\Expr\OriginalForeachValueExpr; @@ -1501,9 +1500,16 @@ public function processStmtNode( } if ($stmt->valueVar instanceof Variable) { - $this->callNodeCallback($nodeCallback, new VariableAssignNode($stmt->valueVar, new GetIterableValueTypeExpr($stmt->expr)), $originalScope, $storage); + $valueTypeExpr = new NativeTypeExpr( + $originalScope->getIterableValueType($originalScope->getType($stmt->expr)), + $originalScope->getIterableValueType($originalScope->getNativeType($stmt->expr)), + ); + $this->callNodeCallback($nodeCallback, new VariableAssignNode($stmt->valueVar, $valueTypeExpr), $originalScope, $storage); } elseif ($stmt->valueVar instanceof List_) { - $virtualAssign = new Assign($stmt->valueVar, new GetIterableValueTypeExpr($stmt->expr)); + $virtualAssign = new Assign($stmt->valueVar, new NativeTypeExpr( + $originalScope->getIterableValueType($originalScope->getType($stmt->expr)), + $originalScope->getIterableValueType($originalScope->getNativeType($stmt->expr)), + )); $virtualAssign->setAttributes($stmt->valueVar->getAttributes()); $this->callNodeCallback($nodeCallback, $virtualAssign, $scope, $storage); } @@ -4502,7 +4508,10 @@ private function enterForeach(MutatingScope $scope, ExpressionResultStorage $sto $storage, $stmt, $stmt->valueVar, - new GetIterableValueTypeExpr($stmt->expr), + new NativeTypeExpr( + $originalScope->getIterableValueType($iterateeType), + $originalScope->getIterableValueType($originalScope->getNativeType($stmt->expr)), + ), $nodeCallback, )->getScope(); $vars = $this->getAssignedVariables($stmt->valueVar); diff --git a/src/Node/Expr/GetIterableValueTypeExpr.php b/src/Node/Expr/GetIterableValueTypeExpr.php deleted file mode 100644 index 642eb4870ea..00000000000 --- a/src/Node/Expr/GetIterableValueTypeExpr.php +++ /dev/null @@ -1,37 +0,0 @@ -expr; - } - - #[Override] - public function getType(): string - { - return 'PHPStan_Node_GetIterableValueTypeExpr'; - } - - /** - * @return string[] - */ - #[Override] - public function getSubNodeNames(): array - { - return []; - } - -} diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index cbe086e6593..61ffd313960 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -14,7 +14,6 @@ use PHPStan\Node\Expr\CloneReinitializationExpr; use PHPStan\Node\Expr\ExistingArrayDimFetch; use PHPStan\Node\Expr\ForeachValueByRefExpr; -use PHPStan\Node\Expr\GetIterableValueTypeExpr; use PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr; use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\Expr\OriginalForeachKeyExpr; @@ -119,11 +118,6 @@ protected function pPHPStan_Node_UnsetOffsetExpr(UnsetOffsetExpr $expr): string return sprintf('__phpstanUnsetOffset(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim())); } - protected function pPHPStan_Node_GetIterableValueTypeExpr(GetIterableValueTypeExpr $expr): string // phpcs:ignore - { - return sprintf('__phpstanGetIterableValueType(%s)', $this->p($expr->getExpr())); - } - protected function pPHPStan_Node_ExistingArrayDimFetch(ExistingArrayDimFetch $expr): string // phpcs:ignore { return sprintf('__phpstanExistingArrayDimFetch(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim())); diff --git a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php index 66b7ca6ce8f..fbd3f1c7bb2 100644 --- a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php +++ b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php @@ -7,7 +7,6 @@ use PhpParser\Node\Expr; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\RegisteredRule; -use PHPStan\Node\Expr\GetIterableValueTypeExpr; use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\InClassMethodNode; use PHPStan\Node\InClassNode; @@ -279,7 +278,11 @@ private function processForeach(Scope $scope, Node\Expr $iterateeExpr, ?Node\Exp $errors[] = $error; } } - foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $valueVar, new GetIterableValueTypeExpr($iterateeExpr), $varTags, $variableNames) as $error) { + $valueTypeExpr = new NativeTypeExpr( + $scope->getIterableValueType($scope->getScopeType($iterateeExpr)), + $scope->getIterableValueType($scope->getScopeNativeType($iterateeExpr)), + ); + foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $valueVar, $valueTypeExpr, $varTags, $variableNames) as $error) { $errors[] = $error; }