Skip to content

Commit 2017318

Browse files
committed
Fixed assigning generic object without a constructor (like SplObjectStorage) to a property
1 parent 6ef87d1 commit 2017318

File tree

4 files changed

+199
-1
lines changed

4 files changed

+199
-1
lines changed

src/Analyser/MutatingScope.php

+36-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
use PHPStan\Type\FloatType;
6464
use PHPStan\Type\Generic\GenericClassStringType;
6565
use PHPStan\Type\Generic\GenericObjectType;
66+
use PHPStan\Type\Generic\TemplateType;
6667
use PHPStan\Type\Generic\TemplateTypeHelper;
6768
use PHPStan\Type\Generic\TemplateTypeMap;
6869
use PHPStan\Type\GenericTypeVariableResolver;
@@ -4565,8 +4566,42 @@ private function exactInstantiation(New_ $node, string $className): ?Type
45654566
return $methodResult;
45664567
}
45674568

4569+
$objectType = new ObjectType($resolvedClassName);
45684570
if (!$classReflection->isGeneric()) {
4569-
return new ObjectType($resolvedClassName);
4571+
return $objectType;
4572+
}
4573+
4574+
$parentNode = $node->getAttribute('parent');
4575+
if (
4576+
(
4577+
$parentNode instanceof Expr\Assign
4578+
|| $parentNode instanceof Expr\AssignRef
4579+
)
4580+
&& $parentNode->var instanceof PropertyFetch
4581+
) {
4582+
$constructorVariant = ParametersAcceptorSelector::selectSingle($constructorMethod->getVariants());
4583+
$classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes();
4584+
$originalClassTemplateTypes = $classTemplateTypes;
4585+
foreach ($constructorVariant->getParameters() as $parameter) {
4586+
TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$classTemplateTypes): Type {
4587+
if ($type instanceof TemplateType && array_key_exists($type->getName(), $classTemplateTypes)) {
4588+
$classTemplateType = $classTemplateTypes[$type->getName()];
4589+
if ($classTemplateType instanceof TemplateType && $classTemplateType->getScope()->equals($type->getScope())) {
4590+
unset($classTemplateTypes[$type->getName()]);
4591+
}
4592+
return $type;
4593+
}
4594+
4595+
return $traverse($type);
4596+
});
4597+
}
4598+
4599+
if (count($classTemplateTypes) === count($originalClassTemplateTypes)) {
4600+
$propertyType = $this->getType($parentNode->var);
4601+
if ($objectType->isSuperTypeOf($propertyType)->yes()) {
4602+
return $propertyType;
4603+
}
4604+
}
45704605
}
45714606

45724607
if ($constructorMethod instanceof DummyConstructorReflection || $constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) {

tests/PHPStan/Analyser/NodeScopeResolverTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -10772,6 +10772,11 @@ public function dataBug4436(): array
1077210772
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4436.php');
1077310773
}
1077410774

10775+
public function dataBug3777(): array
10776+
{
10777+
return $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/bug-3777.php');
10778+
}
10779+
1077510780
/**
1077610781
* @param string $file
1077710782
* @return array<string, mixed[]>
@@ -10988,6 +10993,7 @@ private function gatherAssertTypes(string $file): array
1098810993
* @dataProvider dataBug4500
1098910994
* @dataProvider dataBug4504
1099010995
* @dataProvider dataBug4436
10996+
* @dataProvider dataBug3777
1099110997
* @param string $assertType
1099210998
* @param string $file
1099310999
* @param mixed ...$args

tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php

+22
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,26 @@ public function testTypesAssignedToStaticPropertiesExpressionNames(): void
163163
]);
164164
}
165165

166+
public function testBug3777(): void
167+
{
168+
$this->analyse([__DIR__ . '/data/bug-3777.php'], [
169+
[
170+
'Property Bug3777\Bar::$foo (Bug3777\Foo<stdClass>) does not accept Bug3777\Fooo<object>.',
171+
58,
172+
],
173+
[
174+
'Property Bug3777\Ipsum::$ipsum (Bug3777\Lorem<stdClass, Exception>) does not accept Bug3777\Lorem<Exception, stdClass>.',
175+
95,
176+
],
177+
[
178+
'Property Bug3777\Ipsum2::$lorem2 (Bug3777\Lorem2<stdClass, Exception>) does not accept Bug3777\Lorem2<stdClass, object>.',
179+
129,
180+
],
181+
[
182+
'Property Bug3777\Ipsum2::$ipsum2 (Bug3777\Lorem2<stdClass, Exception>) does not accept Bug3777\Lorem2<Exception, object>.',
183+
131,
184+
],
185+
]);
186+
}
187+
166188
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
namespace Bug3777;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class HelloWorld
8+
{
9+
/**
10+
* @var \SplObjectStorage<\DateTimeImmutable, null>
11+
*/
12+
public $dates;
13+
14+
public function __construct()
15+
{
16+
$this->dates = new \SplObjectStorage();
17+
assertType('SplObjectStorage<DateTimeImmutable, null>', $this->dates);
18+
}
19+
}
20+
21+
/** @template T of object */
22+
class Foo
23+
{
24+
25+
public function __construct()
26+
{
27+
28+
}
29+
30+
}
31+
32+
/** @template T of object */
33+
class Fooo
34+
{
35+
36+
}
37+
38+
class Bar
39+
{
40+
41+
/** @var Foo<\stdClass> */
42+
private $foo;
43+
44+
/** @var Fooo<\stdClass> */
45+
private $fooo;
46+
47+
public function __construct()
48+
{
49+
$this->foo = new Foo();
50+
assertType('Bug3777\Foo<stdClass>', $this->foo);
51+
52+
$this->fooo = new Fooo();
53+
assertType('Bug3777\Fooo<stdClass>', $this->fooo);
54+
}
55+
56+
public function doBar()
57+
{
58+
$this->foo = new Fooo();
59+
assertType('Bug3777\Fooo<object>', $this->foo);
60+
}
61+
62+
}
63+
64+
/**
65+
* @template T of object
66+
* @template U of object
67+
*/
68+
class Lorem
69+
{
70+
71+
/**
72+
* @param T $t
73+
* @param U $u
74+
*/
75+
public function __construct($t, $u)
76+
{
77+
78+
}
79+
80+
}
81+
82+
class Ipsum
83+
{
84+
85+
/** @var Lorem<\stdClass, \Exception> */
86+
private $lorem;
87+
88+
/** @var Lorem<\stdClass, \Exception> */
89+
private $ipsum;
90+
91+
public function __construct()
92+
{
93+
$this->lorem = new Lorem(new \stdClass, new \Exception());
94+
assertType('Bug3777\Lorem<stdClass, Exception>', $this->lorem);
95+
$this->ipsum = new Lorem(new \Exception(), new \stdClass);
96+
assertType('Bug3777\Lorem<Exception, stdClass>', $this->ipsum);
97+
}
98+
99+
}
100+
101+
/**
102+
* @template T of object
103+
* @template U of object
104+
*/
105+
class Lorem2
106+
{
107+
108+
/**
109+
* @param T $t
110+
*/
111+
public function __construct($t)
112+
{
113+
114+
}
115+
116+
}
117+
118+
class Ipsum2
119+
{
120+
121+
/** @var Lorem2<\stdClass, \Exception> */
122+
private $lorem2;
123+
124+
/** @var Lorem2<\stdClass, \Exception> */
125+
private $ipsum2;
126+
127+
public function __construct()
128+
{
129+
$this->lorem2 = new Lorem2(new \stdClass);
130+
assertType('Bug3777\Lorem2<stdClass, object>', $this->lorem2);
131+
$this->ipsum2 = new Lorem2(new \Exception());
132+
assertType('Bug3777\Lorem2<Exception, object>', $this->ipsum2);
133+
}
134+
135+
}

0 commit comments

Comments
 (0)