Skip to content

Commit dab99cb

Browse files
committed
Fix GenericStaticType in @phpstan-self-out
1 parent b4f41c7 commit dab99cb

4 files changed

+118
-29
lines changed

src/Reflection/Dummy/ChangedTypeMethodReflection.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ final class ChangedTypeMethodReflection implements ExtendedMethodReflection
2020
* @param list<ExtendedParametersAcceptor> $variants
2121
* @param list<ExtendedParametersAcceptor>|null $namedArgumentsVariants
2222
*/
23-
public function __construct(private ClassReflection $declaringClass, private ExtendedMethodReflection $reflection, private array $variants, private ?array $namedArgumentsVariants)
23+
public function __construct(
24+
private ClassReflection $declaringClass,
25+
private ExtendedMethodReflection $reflection,
26+
private array $variants,
27+
private ?array $namedArgumentsVariants,
28+
private ?Type $selfOutType,
29+
)
2430
{
2531
}
2632

@@ -126,7 +132,7 @@ public function acceptsNamedArguments(): TrinaryLogic
126132

127133
public function getSelfOutType(): ?Type
128134
{
129-
return $this->reflection->getSelfOutType();
135+
return $this->selfOutType;
130136
}
131137

132138
public function returnsByReference(): TrinaryLogic

src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,13 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass,
114114
? array_map($variantFn, $namedArgumentVariants)
115115
: null;
116116

117-
return new ChangedTypeMethodReflection($declaringClass, $method, $variants, $namedArgumentVariants);
117+
return new ChangedTypeMethodReflection(
118+
$declaringClass,
119+
$method,
120+
$variants,
121+
$namedArgumentVariants,
122+
$method->getSelfOutType() !== null ? $this->transformStaticType($method->getSelfOutType()) : null,
123+
);
118124
}
119125

120126
private function transformStaticType(Type $type): Type

src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php

+42-26
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPStan\Reflection\ResolvedMethodReflection;
1313
use PHPStan\Type\Generic\GenericStaticType;
1414
use PHPStan\Type\StaticType;
15+
use PHPStan\Type\ThisType;
1516
use PHPStan\Type\Type;
1617
use PHPStan\Type\TypeTraverser;
1718
use function array_map;
@@ -79,39 +80,54 @@ public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflectio
7980

8081
private function transformMethodWithStaticType(ClassReflection $declaringClass, ExtendedMethodReflection $method): ExtendedMethodReflection
8182
{
82-
$variantFn = fn (ExtendedParametersAcceptor $acceptor): ExtendedParametersAcceptor => new ExtendedFunctionVariant(
83-
$acceptor->getTemplateTypeMap(),
84-
$acceptor->getResolvedTemplateTypeMap(),
85-
array_map(
86-
fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter(
87-
$parameter->getName(),
88-
$this->transformStaticType($parameter->getType()),
89-
$parameter->isOptional(),
90-
$parameter->passedByReference(),
91-
$parameter->isVariadic(),
92-
$parameter->getDefaultValue(),
93-
$parameter->getNativeType(),
94-
$this->transformStaticType($parameter->getPhpDocType()),
95-
$parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null,
96-
$parameter->isImmediatelyInvokedCallable(),
97-
$parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null,
98-
$parameter->getAttributes(),
83+
$selfOutType = $method->getSelfOutType() !== null ? $this->transformStaticType($method->getSelfOutType()) : null;
84+
$variantFn = function (ExtendedParametersAcceptor $acceptor) use ($selfOutType): ExtendedParametersAcceptor {
85+
$originalReturnType = $acceptor->getReturnType();
86+
if ($originalReturnType instanceof ThisType && $selfOutType !== null) {
87+
$returnType = $selfOutType;
88+
} else {
89+
$returnType = $this->transformStaticType($originalReturnType);
90+
}
91+
return new ExtendedFunctionVariant(
92+
$acceptor->getTemplateTypeMap(),
93+
$acceptor->getResolvedTemplateTypeMap(),
94+
array_map(
95+
fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter(
96+
$parameter->getName(),
97+
$this->transformStaticType($parameter->getType()),
98+
$parameter->isOptional(),
99+
$parameter->passedByReference(),
100+
$parameter->isVariadic(),
101+
$parameter->getDefaultValue(),
102+
$parameter->getNativeType(),
103+
$this->transformStaticType($parameter->getPhpDocType()),
104+
$parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null,
105+
$parameter->isImmediatelyInvokedCallable(),
106+
$parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null,
107+
$parameter->getAttributes(),
108+
),
109+
$acceptor->getParameters(),
99110
),
100-
$acceptor->getParameters(),
101-
),
102-
$acceptor->isVariadic(),
103-
$this->transformStaticType($acceptor->getReturnType()),
104-
$this->transformStaticType($acceptor->getPhpDocReturnType()),
105-
$this->transformStaticType($acceptor->getNativeReturnType()),
106-
$acceptor->getCallSiteVarianceMap(),
107-
);
111+
$acceptor->isVariadic(),
112+
$returnType,
113+
$this->transformStaticType($acceptor->getPhpDocReturnType()),
114+
$this->transformStaticType($acceptor->getNativeReturnType()),
115+
$acceptor->getCallSiteVarianceMap(),
116+
);
117+
};
108118
$variants = array_map($variantFn, $method->getVariants());
109119
$namedArgumentsVariants = $method->getNamedArgumentsVariants();
110120
$namedArgumentsVariants = $namedArgumentsVariants !== null
111121
? array_map($variantFn, $namedArgumentsVariants)
112122
: null;
113123

114-
return new ChangedTypeMethodReflection($declaringClass, $method, $variants, $namedArgumentsVariants);
124+
return new ChangedTypeMethodReflection(
125+
$declaringClass,
126+
$method,
127+
$variants,
128+
$namedArgumentsVariants,
129+
$selfOutType,
130+
);
115131
}
116132

117133
private function transformStaticType(Type $type): Type
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace Bug12575;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template T of object
9+
*/
10+
class Foo
11+
{
12+
13+
/**
14+
* @template U of object
15+
* @param class-string<U> $class
16+
* @return $this
17+
* @phpstan-self-out static<T&U>
18+
*/
19+
public function add(string $class)
20+
{
21+
return $this;
22+
}
23+
24+
}
25+
26+
/**
27+
* @template T of object
28+
* @extends Foo<T>
29+
*/
30+
class Bar extends Foo
31+
{
32+
33+
}
34+
35+
interface A
36+
{
37+
38+
}
39+
40+
interface B
41+
{
42+
43+
}
44+
45+
/**
46+
* @param Bar<A> $bar
47+
* @return void
48+
*/
49+
function doFoo(Bar $bar): void {
50+
assertType('Bug12575\\Bar<Bug12575\\A&Bug12575\\B>', $bar->add(B::class));
51+
assertType('Bug12575\\Bar<Bug12575\\A&Bug12575\\B>', $bar);
52+
};
53+
54+
/**
55+
* @param Bar<A> $bar
56+
* @return void
57+
*/
58+
function doBar(Bar $bar): void {
59+
$bar->add(B::class);
60+
assertType('Bug12575\\Bar<Bug12575\\A&Bug12575\\B>', $bar);
61+
};

0 commit comments

Comments
 (0)