Skip to content

Commit 8c0cec1

Browse files
phpstan-botclaude
andcommitted
Use TypeCombinator::intersect() to extract matching type from property union
When the property type is a union like `WeakMap<object,string>|null|false`, `TypeCombinator::removeNull()` only strips `null`, leaving `WeakMap<object,string>|false`. The subsequent `isSuperTypeOf()` check then fails because `false` is not a subtype of `WeakMap`. Using `TypeCombinator::intersect()` with the class type properly extracts only the matching part of the union (e.g. `WeakMap<object,string>`), handling any combination of non-object types in the property's declared type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 29d20a0 commit 8c0cec1

File tree

3 files changed

+79
-2
lines changed

3 files changed

+79
-2
lines changed

src/Analyser/ExprHandler/NewHandler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,9 @@ private function exactInstantiation(MutatingScope $scope, New_ $node, Name $clas
420420
if (count($classTemplateTypes) === count($originalClassTemplateTypes)) {
421421
$foundProperty = $this->propertyReflectionFinder->findPropertyReflectionFromNode($assignedToProperty, $scope);
422422
if ($foundProperty !== null) {
423-
$propertyType = TypeCombinator::removeNull($foundProperty->getWritableType());
424423
$nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, classReflection: $nonFinalClassReflection);
425-
if ($nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) {
424+
$propertyType = TypeCombinator::intersect($foundProperty->getWritableType(), $nonFinalObjectType);
425+
if (!($propertyType instanceof NeverType) && $nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) {
426426
return $propertyType;
427427
}
428428
}

tests/PHPStan/Analyser/nsrt/bug-11844.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,36 @@ public function initInstance(): void
3434
}
3535
}
3636
}
37+
38+
class Bar
39+
{
40+
/**
41+
* @var \WeakMap<object, string>|null|false
42+
*/
43+
private \WeakMap|null|false $instanceMap = false;
44+
45+
/**
46+
* @var \WeakMap<object, string>|null|false
47+
*/
48+
private static \WeakMap|null|false $staticMap = false;
49+
50+
public function initInstance(): void
51+
{
52+
if ($this->instanceMap !== false) {
53+
if ($this->instanceMap === null) {
54+
$this->instanceMap = new \WeakMap();
55+
assertType('WeakMap<object, string>', $this->instanceMap);
56+
}
57+
}
58+
}
59+
60+
public static function initStatic(): void
61+
{
62+
if (self::$staticMap !== false) {
63+
if (self::$staticMap === null) {
64+
self::$staticMap = new \WeakMap();
65+
assertType('WeakMap<object, string>', self::$staticMap);
66+
}
67+
}
68+
}
69+
}

tests/PHPStan/Rules/TooWideTypehints/data/bug-11844.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,50 @@ public function __construct($value) {
4646
}
4747
}
4848

49+
class NullOrFalsePropertyCase
50+
{
51+
/**
52+
* @var \WeakMap<object, string>|null|false
53+
*/
54+
private \WeakMap|null|false $map = false;
55+
56+
public function init(): void
57+
{
58+
if ($this->map !== false) {
59+
if ($this->map === null) {
60+
$this->map = new \WeakMap();
61+
}
62+
}
63+
}
64+
65+
public function reset(): void
66+
{
67+
$this->map = null;
68+
}
69+
}
70+
71+
class StaticNullOrFalsePropertyCase
72+
{
73+
/**
74+
* @var \WeakMap<object, string>|null|false
75+
*/
76+
private static \WeakMap|null|false $map = false;
77+
78+
public static function init(): void
79+
{
80+
if (self::$map !== false) {
81+
if (self::$map === null) {
82+
self::$map = new \WeakMap();
83+
}
84+
}
85+
}
86+
87+
public static function reset(): void
88+
{
89+
self::$map = null;
90+
}
91+
}
92+
4993
class OtherGenericCase
5094
{
5195
/**

0 commit comments

Comments
 (0)