From 6f9e24a3b7d029d21944445645e4c9007b6b1705 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:03:54 +0000 Subject: [PATCH] Fix phpstan/phpstan#11844: false property.unusedType for nullable generic property assigned inside null check - Used declared property type from reflection instead of scope-narrowed type in NewHandler - When `new WeakMap()` was assigned inside `if ($this->map === null)`, scope returned narrowed `null` type - TypeCombinator::removeNull on `null` yielded `never`, making TooWideTypeCheck think WeakMap was never assigned - New regression test in tests/PHPStan/Rules/TooWideTypehints/data/bug-11844.php --- src/Analyser/ExprHandler/NewHandler.php | 32 +++++++++++-- .../TooWidePropertyTypeRuleTest.php | 6 +++ .../Rules/TooWideTypehints/data/bug-11844.php | 48 +++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-11844.php diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index 67cc17797d..3b23d8201e 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -416,10 +416,13 @@ private function exactInstantiation(MutatingScope $scope, New_ $node, Name $clas $classTemplateTypes = $traverser->getClassTemplateTypes(); if (count($classTemplateTypes) === count($originalClassTemplateTypes)) { - $propertyType = TypeCombinator::removeNull($scope->getType($assignedToProperty)); - $nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, classReflection: $nonFinalClassReflection); - if ($nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) { - return $propertyType; + $propertyType = $this->resolveAssignedPropertyDeclaredType($assignedToProperty, $scope); + if ($propertyType !== null) { + $propertyType = TypeCombinator::removeNull($propertyType); + $nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, classReflection: $nonFinalClassReflection); + if ($nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) { + return $propertyType; + } } } } @@ -590,4 +593,25 @@ classReflection: $classReflection->withTypes($types)->asFinal(), return TypeTraverser::map($newGenericType, new GenericTypeTemplateTraverser($resolvedTemplateTypeMap)); } + private function resolveAssignedPropertyDeclaredType(Node\Expr $propertyFetch, MutatingScope $scope): ?Type + { + if ($propertyFetch instanceof Node\Expr\PropertyFetch && $propertyFetch->name instanceof Node\Identifier) { + $holderType = $scope->getType($propertyFetch->var); + $propertyName = $propertyFetch->name->name; + if ($holderType->hasInstanceProperty($propertyName)->yes()) { + return $holderType->getInstanceProperty($propertyName, $scope)->getReadableType(); + } + } elseif ($propertyFetch instanceof Node\Expr\StaticPropertyFetch && $propertyFetch->name instanceof Node\VarLikeIdentifier && $propertyFetch->class instanceof Name) { + $className = $scope->resolveName($propertyFetch->class); + if ($this->reflectionProvider->hasClass($className)) { + $classRefl = $this->reflectionProvider->getClass($className); + $propertyName = $propertyFetch->name->name; + if ($classRefl->hasStaticProperty($propertyName)) { + return $classRefl->getStaticProperty($propertyName)->getReadableType(); + } + } + } + return null; + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index 4ddf860b4d..cab35b9a6a 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -138,4 +138,10 @@ public function testBug13624(): void $this->analyse([__DIR__ . '/data/bug-13624.php'], []); } + #[RequiresPhp('>= 8.0')] + public function testBug11844(): void + { + $this->analyse([__DIR__ . '/data/bug-11844.php'], []); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11844.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11844.php new file mode 100644 index 0000000000..95a0416bba --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11844.php @@ -0,0 +1,48 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug11844; + +class StaticPropertyCase +{ + /** + * @var \WeakMap|null + */ + private static ?\WeakMap $map = null; + + public static function init(): void + { + if (self::$map === null) { + self::$map = new \WeakMap(); + } + } +} + +class InstancePropertyCase +{ + /** + * @var \WeakMap|null + */ + private ?\WeakMap $map = null; + + public function init(): void + { + if ($this->map === null) { + $this->map = new \WeakMap(); + } + } +} + +class DirectAssignCase +{ + /** + * @var \WeakMap|null + */ + private ?\WeakMap $map = null; + + public function initAlways(): void + { + $this->map = new \WeakMap(); + } +}