Skip to content

Commit dcbf321

Browse files
committed
Fix generic static<...> when the child class is not generic
1 parent 692b018 commit dcbf321

File tree

3 files changed

+39
-5
lines changed

3 files changed

+39
-5
lines changed

src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
use PHPStan\Reflection\ParametersAcceptorWithPhpDocs;
1111
use PHPStan\Reflection\Php\DummyParameterWithPhpDocs;
1212
use PHPStan\Reflection\ResolvedMethodReflection;
13-
use PHPStan\Type\Generic\GenericObjectType;
1413
use PHPStan\Type\Generic\GenericStaticType;
15-
use PHPStan\Type\ObjectType;
1614
use PHPStan\Type\StaticType;
1715
use PHPStan\Type\Type;
1816
use PHPStan\Type\TypeTraverser;
@@ -118,8 +116,11 @@ private function transformStaticType(Type $type): Type
118116
{
119117
return TypeTraverser::map($type, function (Type $type, callable $traverse): Type {
120118
if ($type instanceof GenericStaticType) {
121-
if ($this->calledOnType instanceof ObjectType) {
122-
return $traverse(new GenericObjectType($this->calledOnType->getClassName(), $type->getTypes()));
119+
$calledOnTypeReflections = $this->calledOnType->getObjectClassReflections();
120+
if (count($calledOnTypeReflections) === 1) {
121+
$calledOnTypeReflection = $calledOnTypeReflections[0];
122+
123+
return $traverse($type->changeBaseClass($calledOnTypeReflection)->getStaticObjectType());
123124
}
124125

125126
return $this->calledOnType;

src/Type/Generic/GenericStaticType.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
88
use PHPStan\Reflection\ClassReflection;
99
use PHPStan\Reflection\ReflectionProviderStaticAccessor;
10+
use PHPStan\ShouldNotHappenException;
1011
use PHPStan\Type\CompoundType;
1112
use PHPStan\Type\ErrorType;
1213
use PHPStan\Type\IsSuperTypeOfResult;
@@ -40,6 +41,9 @@ public function __construct(
4041
private array $variances,
4142
)
4243
{
44+
if (count($this->types) === 0) {
45+
throw new ShouldNotHappenException('Cannot create GenericStaticType with zero types.');
46+
}
4347
parent::__construct($classReflection, $subtractedType);
4448
$this->baseClass = $classReflection->getName();
4549
}
@@ -88,6 +92,10 @@ public function changeBaseClass(ClassReflection $classReflection): StaticType
8892
return $this;
8993
}
9094

95+
if (!$classReflection->isGeneric()) {
96+
return new StaticType($classReflection);
97+
}
98+
9199
// this template type mapping logic is very similar to mapping logic in MutatingScope::exactInstantiation()
92100
// where inferring "new Foo" but with the constructor being only in Foo parent class
93101

tests/PHPStan/Analyser/nsrt/generic-static.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public function doBar(self $s): void
7474

7575
assertType('GenericStatic\\FooImpl<float, string>', $s->flip());
7676
assertType('GenericStatic\\FooImpl<string, float>', $s->fluent());
77-
assertType('GenericStatic\FooImpl<string, GenericStatic\FooImpl<float>>', $s->nested());
77+
assertType('GenericStatic\FooImpl<string, GenericStatic\FooImpl<float, mixed>>', $s->nested());
7878
}
7979

8080
}
@@ -97,6 +97,7 @@ public function fluent()
9797
*/
9898
public function test(self $s): void
9999
{
100+
assertType('static(GenericStatic\Inconsistent<T (class GenericStatic\Inconsistent, argument), U (class GenericStatic\Inconsistent, argument)>)', $this->fluent());
100101
assertType('GenericStatic\\Inconsistent<int, string>', $s->fluent());
101102
}
102103

@@ -119,7 +120,31 @@ public function fluent()
119120
*/
120121
public function test(self $s): void
121122
{
123+
assertType('static(GenericStatic\Inconsistent2<T (class GenericStatic\Inconsistent2, argument)>)', $this->fluent());
122124
assertType('GenericStatic\\Inconsistent2<int>', $s->fluent());
123125
}
124126

125127
}
128+
129+
/**
130+
* @template T
131+
* @template K
132+
*/
133+
class A {
134+
/** @return static<T, K> */
135+
public function doFoo() {}
136+
137+
}
138+
139+
/** @extends A<int, string> */
140+
class B extends A {
141+
public function doBar(): void
142+
{
143+
$f = $this->doFoo();
144+
assertType('static(GenericStatic\B)', $f);
145+
}
146+
}
147+
148+
function (): void {
149+
assertType(B::class, (new B)->doFoo());
150+
};

0 commit comments

Comments
 (0)