Skip to content

Commit ec1555b

Browse files
committed
Merge branch 2.1.x into 2.2.x
2 parents d6a4374 + 5ec9767 commit ec1555b

File tree

3 files changed

+226
-4
lines changed

3 files changed

+226
-4
lines changed

src/Analyser/ExprHandler/NewHandler.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use PHPStan\Reflection\ParametersAcceptor;
3636
use PHPStan\Reflection\ParametersAcceptorSelector;
3737
use PHPStan\Reflection\ReflectionProvider;
38+
use PHPStan\Rules\Properties\PropertyReflectionFinder;
3839
use PHPStan\ShouldNotHappenException;
3940
use PHPStan\Type\ErrorType;
4041
use PHPStan\Type\Generic\GenericObjectType;
@@ -66,6 +67,7 @@ public function __construct(
6667
private ReflectionProvider $reflectionProvider,
6768
private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
6869
private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider,
70+
private PropertyReflectionFinder $propertyReflectionFinder,
6971
#[AutowiredParameter(ref: '%exceptions.implicitThrows%')]
7072
private bool $implicitThrows,
7173
)
@@ -416,10 +418,13 @@ private function exactInstantiation(MutatingScope $scope, New_ $node, Name $clas
416418
$classTemplateTypes = $traverser->getClassTemplateTypes();
417419

418420
if (count($classTemplateTypes) === count($originalClassTemplateTypes)) {
419-
$propertyType = TypeCombinator::removeNull($scope->getType($assignedToProperty));
420-
$nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, classReflection: $nonFinalClassReflection);
421-
if ($nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) {
422-
return $propertyType;
421+
$foundProperty = $this->propertyReflectionFinder->findPropertyReflectionFromNode($assignedToProperty, $scope);
422+
if ($foundProperty !== null) {
423+
$nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, classReflection: $nonFinalClassReflection);
424+
$propertyType = TypeCombinator::intersect($foundProperty->getWritableType(), $nonFinalObjectType);
425+
if (!$propertyType instanceof NeverType) {
426+
return $propertyType;
427+
}
423428
}
424429
}
425430
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug11844;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class StaticPropertyCase
10+
{
11+
/**
12+
* @var \WeakMap<object, string>|null
13+
*/
14+
private static ?\WeakMap $map = null;
15+
16+
public static function init(): void
17+
{
18+
if (self::$map === null) {
19+
self::$map = new \WeakMap();
20+
assertType('WeakMap<object, string>', self::$map);
21+
}
22+
}
23+
}
24+
25+
class InstancePropertyCase
26+
{
27+
/**
28+
* @var \WeakMap<object, string>|null
29+
*/
30+
private ?\WeakMap $map = null;
31+
32+
public function init(): void
33+
{
34+
if ($this->map === null) {
35+
$this->map = new \WeakMap();
36+
assertType('WeakMap<object, string>', $this->map);
37+
}
38+
}
39+
}
40+
41+
/** @template T */
42+
class GenericContainer
43+
{
44+
/** @var T */
45+
private $value;
46+
47+
/** @param T $value */
48+
public function __construct($value) {
49+
$this->value = $value;
50+
}
51+
}
52+
53+
class NullOrFalsePropertyCase
54+
{
55+
/**
56+
* @var \WeakMap<object, string>|null|false
57+
*/
58+
private \WeakMap|null|false $map = false;
59+
60+
public function init(): void
61+
{
62+
if ($this->map !== false) {
63+
if ($this->map === null) {
64+
$this->map = new \WeakMap();
65+
assertType('WeakMap<object, string>', $this->map);
66+
}
67+
}
68+
}
69+
70+
public function reset(): void
71+
{
72+
$this->map = null;
73+
}
74+
}
75+
76+
class StaticNullOrFalsePropertyCase
77+
{
78+
/**
79+
* @var \WeakMap<object, string>|null|false
80+
*/
81+
private static \WeakMap|null|false $map = false;
82+
83+
public static function init(): void
84+
{
85+
if (self::$map !== false) {
86+
if (self::$map === null) {
87+
self::$map = new \WeakMap();
88+
assertType('WeakMap<object, string>', self::$map);
89+
}
90+
}
91+
}
92+
93+
public static function reset(): void
94+
{
95+
self::$map = null;
96+
}
97+
}
98+
99+
class OtherGenericCase
100+
{
101+
/**
102+
* @var \SplObjectStorage<object, string>|null
103+
*/
104+
private static ?\SplObjectStorage $storage = null;
105+
106+
public static function init(): void
107+
{
108+
if (self::$storage === null) {
109+
self::$storage = new \SplObjectStorage();
110+
assertType('SplObjectStorage<object, string>', self::$storage);
111+
}
112+
}
113+
}
114+
115+
/**
116+
* Custom generic class whose constructor does NOT reference template types.
117+
* This proves the fix is general, not WeakMap-specific.
118+
*
119+
* @template TKey of string
120+
* @template TValue
121+
*/
122+
class CustomGenericCache
123+
{
124+
/** @var array<TKey, TValue> */
125+
private array $data = [];
126+
127+
public function __construct()
128+
{
129+
}
130+
131+
/**
132+
* @param TKey $key
133+
* @param TValue $value
134+
*/
135+
public function set(string $key, mixed $value): void
136+
{
137+
$this->data[$key] = $value;
138+
}
139+
}
140+
141+
class CustomGenericPropertyCase
142+
{
143+
/**
144+
* @var CustomGenericCache<string, int>|null
145+
*/
146+
private ?CustomGenericCache $cache = null;
147+
148+
public function init(): void
149+
{
150+
if ($this->cache === null) {
151+
$this->cache = new CustomGenericCache();
152+
assertType('Bug11844\CustomGenericCache<string, int>', $this->cache);
153+
}
154+
}
155+
}
156+
157+
class StaticCustomGenericPropertyCase
158+
{
159+
/**
160+
* @var CustomGenericCache<string, int>|null
161+
*/
162+
private static ?CustomGenericCache $cache = null;
163+
164+
public static function init(): void
165+
{
166+
if (self::$cache === null) {
167+
self::$cache = new CustomGenericCache();
168+
assertType('Bug11844\CustomGenericCache<string, int>', self::$cache);
169+
}
170+
}
171+
}
172+
173+
/**
174+
* @template T of object
175+
* @template U
176+
*/
177+
class TemplatePropertyCase
178+
{
179+
/**
180+
* @var \WeakMap<T, U>|null
181+
*/
182+
private ?\WeakMap $map = null;
183+
184+
public function init(): void
185+
{
186+
if ($this->map === null) {
187+
$this->map = new \WeakMap();
188+
assertType('WeakMap<T of object (class Bug11844\TemplatePropertyCase, argument), U (class Bug11844\TemplatePropertyCase, argument)>', $this->map);
189+
}
190+
}
191+
}
192+
193+
/**
194+
* @template T of object
195+
* @template U
196+
*/
197+
class StaticTemplatePropertyCase
198+
{
199+
/**
200+
* @var \WeakMap<T, U>|null
201+
*/
202+
private static ?\WeakMap $map = null;
203+
204+
public static function init(): void
205+
{
206+
if (self::$map === null) {
207+
self::$map = new \WeakMap();
208+
assertType('WeakMap<T of object (class Bug11844\StaticTemplatePropertyCase, argument), U (class Bug11844\StaticTemplatePropertyCase, argument)>', self::$map);
209+
}
210+
}
211+
}

tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,10 @@ public function testBug13624(): void
138138
$this->analyse([__DIR__ . '/data/bug-13624.php'], []);
139139
}
140140

141+
#[RequiresPhp('>= 8.0')]
142+
public function testBug11844(): void
143+
{
144+
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-11844.php'], []);
145+
}
146+
141147
}

0 commit comments

Comments
 (0)