Skip to content

Commit a063119

Browse files
committed
Fix inferring type of new with generic type with constructor in parent class
1 parent b5fc9ec commit a063119

File tree

3 files changed

+200
-2
lines changed

3 files changed

+200
-2
lines changed

src/Analyser/MutatingScope.php

+65-1
Original file line numberDiff line numberDiff line change
@@ -5525,13 +5525,77 @@ private function exactInstantiation(New_ $node, string $className): ?Type
55255525
}
55265526
}
55275527

5528-
if ($constructorMethod instanceof DummyConstructorReflection || $constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) {
5528+
if ($constructorMethod instanceof DummyConstructorReflection) {
55295529
return new GenericObjectType(
55305530
$resolvedClassName,
55315531
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
55325532
);
55335533
}
55345534

5535+
if ($constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) {
5536+
if (!$constructorMethod->getDeclaringClass()->isGeneric()) {
5537+
return new GenericObjectType(
5538+
$resolvedClassName,
5539+
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5540+
);
5541+
}
5542+
$newType = new GenericObjectType($resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()));
5543+
$ancestorType = $newType->getAncestorWithClassName($constructorMethod->getDeclaringClass()->getName());
5544+
if ($ancestorType === null) {
5545+
return new GenericObjectType(
5546+
$resolvedClassName,
5547+
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5548+
);
5549+
}
5550+
$ancestorClassReflections = $ancestorType->getObjectClassReflections();
5551+
if (count($ancestorClassReflections) !== 1) {
5552+
return new GenericObjectType(
5553+
$resolvedClassName,
5554+
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5555+
);
5556+
}
5557+
5558+
$newParentNode = new New_(new Name($constructorMethod->getDeclaringClass()->getName()), $node->args);
5559+
$newParentType = $this->getType($newParentNode);
5560+
$newParentTypeClassReflections = $newParentType->getObjectClassReflections();
5561+
if (count($newParentTypeClassReflections) !== 1) {
5562+
return new GenericObjectType(
5563+
$resolvedClassName,
5564+
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5565+
);
5566+
}
5567+
$newParentTypeClassReflection = $newParentTypeClassReflections[0];
5568+
5569+
$ancestorClassReflection = $ancestorClassReflections[0];
5570+
$ancestorMapping = [];
5571+
foreach ($ancestorClassReflection->getActiveTemplateTypeMap()->getTypes() as $typeName => $templateType) {
5572+
if (!$templateType instanceof TemplateType) {
5573+
continue;
5574+
}
5575+
5576+
$ancestorMapping[$typeName] = $templateType->getName();
5577+
}
5578+
5579+
$resolvedTypeMap = [];
5580+
foreach ($newParentTypeClassReflection->getActiveTemplateTypeMap()->getTypes() as $typeName => $type) {
5581+
if (!array_key_exists($typeName, $ancestorMapping)) {
5582+
continue;
5583+
}
5584+
5585+
if (!array_key_exists($ancestorMapping[$typeName], $resolvedTypeMap)) {
5586+
$resolvedTypeMap[$ancestorMapping[$typeName]] = $type;
5587+
continue;
5588+
}
5589+
5590+
$resolvedTypeMap[$ancestorMapping[$typeName]] = TypeCombinator::union($resolvedTypeMap[$ancestorMapping[$typeName]], $type);
5591+
}
5592+
5593+
return new GenericObjectType(
5594+
$resolvedClassName,
5595+
$classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap)),
5596+
);
5597+
}
5598+
55355599
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
55365600
$this,
55375601
$methodCall->getArgs(),
+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
namespace Bug2735;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Dog {}
8+
9+
class Cat {}
10+
11+
/**
12+
* @template T
13+
*/
14+
class Collection {
15+
/** @var array<T> */
16+
protected $arr = [];
17+
18+
/**
19+
* @param array<T> $arr
20+
*/
21+
public function __construct(array $arr) {
22+
$this->arr = $arr;
23+
}
24+
25+
/**
26+
* @return T
27+
*/
28+
public function last()
29+
{
30+
if (!$this->arr) {
31+
throw new \Exception('bad');
32+
}
33+
return end($this->arr);
34+
}
35+
}
36+
37+
/**
38+
* @template T
39+
* @extends Collection<T>
40+
*/
41+
class CollectionChild extends Collection {
42+
}
43+
44+
$dogs = new CollectionChild([new Dog(), new Dog()]);
45+
assertType('Bug2735\\CollectionChild<Bug2735\\Dog>', $dogs);
46+
47+
/**
48+
* @template X
49+
* @template Y
50+
*/
51+
class ParentWithConstructor
52+
{
53+
54+
/**
55+
* @param X $x
56+
* @param Y $y
57+
*/
58+
public function __construct($x, $y)
59+
{
60+
}
61+
62+
}
63+
64+
/**
65+
* @template T
66+
* @extends ParentWithConstructor<int, T>
67+
*/
68+
class ChildOne extends ParentWithConstructor
69+
{
70+
71+
}
72+
73+
function (): void {
74+
$a = new ChildOne(1, new Dog());
75+
assertType('Bug2735\\ChildOne<Bug2735\\Dog>', $a);
76+
};
77+
78+
/**
79+
* @template T
80+
* @extends ParentWithConstructor<T, int>
81+
*/
82+
class ChildTwo extends ParentWithConstructor
83+
{
84+
85+
}
86+
87+
function (): void {
88+
$a = new ChildTwo(new Cat(), 2);
89+
assertType('Bug2735\\ChildTwo<Bug2735\\Cat>', $a);
90+
};
91+
92+
/**
93+
* @template T
94+
* @extends ParentWithConstructor<T, T>
95+
*/
96+
class ChildThree extends ParentWithConstructor
97+
{
98+
99+
}
100+
101+
function (): void {
102+
$a = new ChildThree(new Cat(), new Dog());
103+
assertType('Bug2735\\ChildThree<Bug2735\\Cat|Bug2735\\Dog>', $a);
104+
};
105+
106+
/**
107+
* @template T
108+
* @template U
109+
* @extends ParentWithConstructor<T, U>
110+
*/
111+
class ChildFour extends ParentWithConstructor
112+
{
113+
114+
}
115+
116+
function (): void {
117+
$a = new ChildFour(new Cat(), new Dog());
118+
assertType('Bug2735\\ChildFour<Bug2735\\Cat, Bug2735\\Dog>', $a);
119+
};
120+
121+
/**
122+
* @template T
123+
* @template U
124+
* @extends ParentWithConstructor<U, T>
125+
*/
126+
class ChildFive extends ParentWithConstructor
127+
{
128+
129+
}
130+
131+
function (): void {
132+
$a = new ChildFive(new Cat(), new Dog());
133+
assertType('Bug2735\\ChildFive<Bug2735\\Dog, Bug2735\\Cat>', $a);
134+
};

tests/PHPStan/Analyser/nsrt/generics.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ function testClasses()
741741
assertType('DateTime', $ab->getB(new \DateTime()));
742742

743743
$noConstructor = new NoConstructor(1);
744-
assertType('PHPStan\Generics\FunctionsAssertType\NoConstructor<mixed>', $noConstructor);
744+
assertType('PHPStan\Generics\FunctionsAssertType\NoConstructor<int>', $noConstructor);
745745

746746
assertType('stdClass', acceptsClassString(\stdClass::class));
747747
assertType('class-string<stdClass>', returnsClassString(new \stdClass()));

0 commit comments

Comments
 (0)