diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index 67cc17797d5..2e57336cb9e 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -35,6 +35,7 @@ use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\GenericObjectType; @@ -66,6 +67,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider, + private PropertyReflectionFinder $propertyReflectionFinder, #[AutowiredParameter(ref: '%exceptions.implicitThrows%')] private bool $implicitThrows, ) @@ -416,10 +418,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; + $foundProperty = $this->propertyReflectionFinder->findPropertyReflectionFromNode($assignedToProperty, $scope); + if ($foundProperty !== null) { + $nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, classReflection: $nonFinalClassReflection); + $propertyType = TypeCombinator::intersect($foundProperty->getWritableType(), $nonFinalObjectType); + if (!$propertyType instanceof NeverType) { + return $propertyType; + } } } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11844.php b/tests/PHPStan/Analyser/nsrt/bug-11844.php new file mode 100644 index 00000000000..0db69f91639 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11844.php @@ -0,0 +1,211 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug11844; + +use function PHPStan\Testing\assertType; + +class StaticPropertyCase +{ + /** + * @var \WeakMap|null + */ + private static ?\WeakMap $map = null; + + public static function init(): void + { + if (self::$map === null) { + self::$map = new \WeakMap(); + assertType('WeakMap', self::$map); + } + } +} + +class InstancePropertyCase +{ + /** + * @var \WeakMap|null + */ + private ?\WeakMap $map = null; + + public function init(): void + { + if ($this->map === null) { + $this->map = new \WeakMap(); + assertType('WeakMap', $this->map); + } + } +} + +/** @template T */ +class GenericContainer +{ + /** @var T */ + private $value; + + /** @param T $value */ + public function __construct($value) { + $this->value = $value; + } +} + +class NullOrFalsePropertyCase +{ + /** + * @var \WeakMap|null|false + */ + private \WeakMap|null|false $map = false; + + public function init(): void + { + if ($this->map !== false) { + if ($this->map === null) { + $this->map = new \WeakMap(); + assertType('WeakMap', $this->map); + } + } + } + + public function reset(): void + { + $this->map = null; + } +} + +class StaticNullOrFalsePropertyCase +{ + /** + * @var \WeakMap|null|false + */ + private static \WeakMap|null|false $map = false; + + public static function init(): void + { + if (self::$map !== false) { + if (self::$map === null) { + self::$map = new \WeakMap(); + assertType('WeakMap', self::$map); + } + } + } + + public static function reset(): void + { + self::$map = null; + } +} + +class OtherGenericCase +{ + /** + * @var \SplObjectStorage|null + */ + private static ?\SplObjectStorage $storage = null; + + public static function init(): void + { + if (self::$storage === null) { + self::$storage = new \SplObjectStorage(); + assertType('SplObjectStorage', self::$storage); + } + } +} + +/** + * Custom generic class whose constructor does NOT reference template types. + * This proves the fix is general, not WeakMap-specific. + * + * @template TKey of string + * @template TValue + */ +class CustomGenericCache +{ + /** @var array */ + private array $data = []; + + public function __construct() + { + } + + /** + * @param TKey $key + * @param TValue $value + */ + public function set(string $key, mixed $value): void + { + $this->data[$key] = $value; + } +} + +class CustomGenericPropertyCase +{ + /** + * @var CustomGenericCache|null + */ + private ?CustomGenericCache $cache = null; + + public function init(): void + { + if ($this->cache === null) { + $this->cache = new CustomGenericCache(); + assertType('Bug11844\CustomGenericCache', $this->cache); + } + } +} + +class StaticCustomGenericPropertyCase +{ + /** + * @var CustomGenericCache|null + */ + private static ?CustomGenericCache $cache = null; + + public static function init(): void + { + if (self::$cache === null) { + self::$cache = new CustomGenericCache(); + assertType('Bug11844\CustomGenericCache', self::$cache); + } + } +} + +/** + * @template T of object + * @template U + */ +class TemplatePropertyCase +{ + /** + * @var \WeakMap|null + */ + private ?\WeakMap $map = null; + + public function init(): void + { + if ($this->map === null) { + $this->map = new \WeakMap(); + assertType('WeakMap', $this->map); + } + } +} + +/** + * @template T of object + * @template U + */ +class StaticTemplatePropertyCase +{ + /** + * @var \WeakMap|null + */ + private static ?\WeakMap $map = null; + + public static function init(): void + { + if (self::$map === null) { + self::$map = new \WeakMap(); + assertType('WeakMap', self::$map); + } + } +} diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index 4ddf860b4de..dbfb5258b84 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__ . '/../../Analyser/nsrt/bug-11844.php'], []); + } + }