Skip to content

Commit 1c2b727

Browse files
committed
Fix checking overriden method signature when method-level generics are involved
1 parent 3a57521 commit 1c2b727

File tree

6 files changed

+227
-6
lines changed

6 files changed

+227
-6
lines changed

src/Rules/Methods/MethodSignatureRule.php

+13-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
1212
use PHPStan\Rules\RuleErrorBuilder;
1313
use PHPStan\TrinaryLogic;
14+
use PHPStan\Type\Generic\TemplateTypeHelper;
1415
use PHPStan\Type\MixedType;
1516
use PHPStan\Type\Type;
1617
use PHPStan\Type\TypehintHelper;
@@ -154,11 +155,11 @@ private function checkReturnTypeCompatibility(
154155
{
155156
$returnType = TypehintHelper::decideType(
156157
$currentVariant->getNativeReturnType(),
157-
$currentVariant->getPhpDocReturnType()
158+
TemplateTypeHelper::resolveToBounds($currentVariant->getPhpDocReturnType())
158159
);
159160
$parentReturnType = TypehintHelper::decideType(
160161
$parentVariant->getNativeReturnType(),
161-
$parentVariant->getPhpDocReturnType()
162+
TemplateTypeHelper::resolveToBounds($parentVariant->getPhpDocReturnType())
162163
);
163164
// Allow adding `void` return type hints when the parent defines no return type
164165
if ($returnType instanceof VoidType && $parentReturnType instanceof MixedType) {
@@ -170,7 +171,10 @@ private function checkReturnTypeCompatibility(
170171
return [TrinaryLogic::createYes(), $returnType, $parentReturnType];
171172
}
172173

173-
return [$parentReturnType->isSuperTypeOf($returnType), $returnType, $parentReturnType];
174+
return [$parentReturnType->isSuperTypeOf($returnType), TypehintHelper::decideType(
175+
$currentVariant->getNativeReturnType(),
176+
$currentVariant->getPhpDocReturnType()
177+
), $parentReturnType];
174178
}
175179

176180
/**
@@ -192,14 +196,17 @@ private function checkParameterTypeCompatibility(
192196

193197
$parameterType = TypehintHelper::decideType(
194198
$parameter->getNativeType(),
195-
$parameter->getPhpDocType()
199+
TemplateTypeHelper::resolveToBounds($parameter->getPhpDocType())
196200
);
197201
$parentParameterType = TypehintHelper::decideType(
198202
$parentParameter->getNativeType(),
199-
$parentParameter->getPhpDocType()
203+
TemplateTypeHelper::resolveToBounds($parentParameter->getPhpDocType())
200204
);
201205

202-
$parameterResults[] = [$parameterType->isSuperTypeOf($parentParameterType), $parameterType, $parentParameterType];
206+
$parameterResults[] = [$parameterType->isSuperTypeOf($parentParameterType), TypehintHelper::decideType(
207+
$parameter->getNativeType(),
208+
$parameter->getPhpDocType()
209+
), $parentParameterType];
203210
}
204211

205212
return $parameterResults;

tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php

+38
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,42 @@ public function testBug4003(): void
239239
]);
240240
}
241241

242+
public function testBug4017(): void
243+
{
244+
$this->reportMaybes = true;
245+
$this->reportStatic = true;
246+
$this->analyse([__DIR__ . '/data/bug-4017.php'], []);
247+
}
248+
249+
public function testBug4017Two(): void
250+
{
251+
$this->reportMaybes = true;
252+
$this->reportStatic = true;
253+
$this->analyse([__DIR__ . '/data/bug-4017_2.php'], [
254+
[
255+
'Parameter #1 $a (Bug4017_2\Foo) of method Bug4017_2\Lorem::doFoo() should be compatible with parameter $a (stdClass) of method Bug4017_2\Bar<stdClass>::doFoo()',
256+
51,
257+
],
258+
]);
259+
}
260+
261+
public function testBug4017Three(): void
262+
{
263+
$this->reportMaybes = true;
264+
$this->reportStatic = true;
265+
$this->analyse([__DIR__ . '/data/bug-4017_3.php'], [
266+
[
267+
'Parameter #1 $a (T of stdClass) of method Bug4017_3\Lorem::doFoo() should be compatible with parameter $a (Bug4017_3\Foo) of method Bug4017_3\Bar::doFoo()',
268+
45,
269+
],
270+
]);
271+
}
272+
273+
public function testBug4023(): void
274+
{
275+
$this->reportMaybes = true;
276+
$this->reportStatic = true;
277+
$this->analyse([__DIR__ . '/data/bug-4023.php'], []);
278+
}
279+
242280
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Bug4017;
4+
5+
/**
6+
* @template T
7+
*/
8+
interface DoctrineEntityRepository
9+
{
10+
11+
}
12+
13+
interface DoctrineEntityManagerInterface
14+
{
15+
/**
16+
* @template T
17+
* @param class-string<T> $className
18+
* @return DoctrineEntityRepository<T>
19+
*/
20+
public function getRepository(string $className): DoctrineEntityRepository;
21+
}
22+
23+
24+
/**
25+
* @phpstan-template TEntityClass
26+
* @phpstan-extends DoctrineEntityRepository<TEntityClass>
27+
*/
28+
interface MyEntityRepositoryInterface extends DoctrineEntityRepository
29+
{
30+
}
31+
32+
interface MyEntityManagerInterface extends DoctrineEntityManagerInterface
33+
{
34+
/**
35+
* @template T
36+
* @param class-string<T> $className
37+
* @return MyEntityRepositoryInterface<T>
38+
*/
39+
public function getRepository(string $className): MyEntityRepositoryInterface;
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace Bug4017_2;
4+
5+
class Foo
6+
{
7+
8+
}
9+
10+
/**
11+
* @template T
12+
*/
13+
class Bar
14+
{
15+
16+
/**
17+
* @param T $a
18+
*/
19+
public function doFoo($a)
20+
{
21+
22+
}
23+
24+
}
25+
26+
/**
27+
* @extends Bar<Foo>
28+
*/
29+
class Baz extends Bar
30+
{
31+
32+
/**
33+
* @param Foo $a
34+
*/
35+
public function doFoo($a)
36+
{
37+
38+
}
39+
40+
}
41+
42+
/**
43+
* @extends Bar<\stdClass>
44+
*/
45+
class Lorem extends Bar
46+
{
47+
48+
/**
49+
* @param Foo $a
50+
*/
51+
public function doFoo($a)
52+
{
53+
54+
}
55+
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Bug4017_3;
4+
5+
class Foo
6+
{
7+
8+
}
9+
10+
class Bar
11+
{
12+
13+
/**
14+
* @template T of Foo
15+
* @param T $a
16+
*/
17+
public function doFoo($a)
18+
{
19+
20+
}
21+
22+
}
23+
24+
class Baz extends Bar
25+
{
26+
27+
/**
28+
* @template T of Foo
29+
* @param T $a
30+
*/
31+
public function doFoo($a)
32+
{
33+
34+
}
35+
36+
}
37+
38+
class Lorem extends Bar
39+
{
40+
41+
/**
42+
* @template T of \stdClass
43+
* @param T $a
44+
*/
45+
public function doFoo($a)
46+
{
47+
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Bug4023;
4+
5+
interface A
6+
{
7+
/**
8+
* @template T of object
9+
*
10+
* @param mixed[]|T $data
11+
*
12+
* @return T
13+
*/
14+
public function x($data): object;
15+
}
16+
17+
final class B implements A
18+
{
19+
/**
20+
* @template T of object
21+
*
22+
* @param mixed[]|T $data
23+
*
24+
* @return T
25+
*/
26+
public function x($data): object
27+
{
28+
throw new \Exception();
29+
}
30+
}

0 commit comments

Comments
 (0)