Skip to content

Commit e1fa4b3

Browse files
committed
Fix ClassAncestorsRule for nested generics
1 parent 200be1a commit e1fa4b3

4 files changed

Lines changed: 84 additions & 12 deletions

File tree

src/Rules/Generics/GenericObjectTypeCheck.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,27 @@ public function check(
6464
))->build();
6565
}
6666

67-
foreach ($templateTypes as $i => $templateType) {
67+
$templateTypesCount = count($templateTypes);
68+
for ($i = 0; $i < $templateTypesCount; $i++) {
6869
if (!isset($genericTypeTypes[$i])) {
6970
continue;
7071
}
7172

72-
$boundType = $templateType;
73-
if ($templateType instanceof TemplateType) {
74-
$boundType = $templateType->getBound();
75-
}
73+
$templateType = $templateTypes[$i];
74+
$boundType = TemplateTypeHelper::resolveToBounds($templateType);
7675
$genericTypeType = $genericTypeTypes[$i];
7776
if ($boundType->isSuperTypeOf($genericTypeType)->yes()) {
77+
if (!$templateType instanceof TemplateType) {
78+
continue;
79+
}
80+
$map = $templateType->inferTemplateTypes($genericTypeType);
81+
for ($j = 0; $j < $templateTypesCount; $j++) {
82+
if ($i === $j) {
83+
continue;
84+
}
85+
86+
$templateTypes[$j] = TemplateTypeHelper::resolveTemplateTypes($templateTypes[$j], $map);
87+
}
7888
continue;
7989
}
8090

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ public function testBug3769(): void
327327
public function testBug3922(): void
328328
{
329329
$errors = $this->runAnalyse(__DIR__ . '/data/bug-3922-integration.php');
330-
$this->assertCount(1, $errors); // expected 0
330+
$this->assertCount(0, $errors);
331331
}
332332

333333
/**

tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,16 +187,18 @@ public function testRuleImplements(): void
187187
public function testBug3922(): void
188188
{
189189
$this->analyse([__DIR__ . '/data/bug-3922-ancestors.php'], [
190-
/*[
190+
[
191191
'Type Bug3922Ancestors\BarQuery in generic type Bug3922Ancestors\QueryHandlerInterface<string, Bug3922Ancestors\BarQuery> in PHPDoc tag @implements is not subtype of template type TQuery of Bug3922Ancestors\QueryInterface<string> of interface Bug3922Ancestors\QueryHandlerInterface.',
192192
54,
193-
],*/
194-
[
195-
'Type Bug3922Ancestors\FooQuery in generic type Bug3922Ancestors\QueryHandlerInterface<string, Bug3922Ancestors\FooQuery> in PHPDoc tag @implements is not subtype of template type TQuery of Bug3922Ancestors\QueryInterface<TResult> of interface Bug3922Ancestors\QueryHandlerInterface.',
196-
43,
197193
],
194+
]);
195+
}
196+
197+
public function testBug3922Reversed(): void
198+
{
199+
$this->analyse([__DIR__ . '/data/bug-3922-ancestors-reversed.php'], [
198200
[
199-
'Type Bug3922Ancestors\BarQuery in generic type Bug3922Ancestors\QueryHandlerInterface<string, Bug3922Ancestors\BarQuery> in PHPDoc tag @implements is not subtype of template type TQuery of Bug3922Ancestors\QueryInterface<TResult> of interface Bug3922Ancestors\QueryHandlerInterface.',
201+
'Type string in generic type Bug3922AncestorsReversed\QueryHandlerInterface<Bug3922AncestorsReversed\BarQuery, string> in PHPDoc tag @implements is not subtype of template type int of interface Bug3922AncestorsReversed\QueryHandlerInterface.',
200202
54,
201203
],
202204
]);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Bug3922AncestorsReversed;
4+
5+
/**
6+
* @template TQuery of QueryInterface<TResult>
7+
* @template TResult
8+
*/
9+
interface QueryHandlerInterface
10+
{
11+
/**
12+
* @param TQuery $query
13+
*
14+
* @return TResult
15+
*/
16+
public function handle(QueryInterface $query);
17+
}
18+
19+
/**
20+
* @template TResult
21+
*/
22+
interface QueryInterface
23+
{
24+
}
25+
26+
/**
27+
* @template-implements QueryInterface<string>
28+
*/
29+
final class FooQuery implements QueryInterface
30+
{
31+
}
32+
33+
/**
34+
* @template-implements QueryInterface<int>
35+
*/
36+
final class BarQuery implements QueryInterface
37+
{
38+
}
39+
40+
/**
41+
* @template-implements QueryHandlerInterface<FooQuery, string>
42+
*/
43+
final class FooQueryHandler implements QueryHandlerInterface
44+
{
45+
public function handle(QueryInterface $query): string
46+
{
47+
return 'foo';
48+
}
49+
}
50+
51+
/**
52+
* @template-implements QueryHandlerInterface<BarQuery, string>
53+
*/
54+
final class BarQueryHandler implements QueryHandlerInterface
55+
{
56+
public function handle(QueryInterface $query): int
57+
{
58+
return 10;
59+
}
60+
}

0 commit comments

Comments
 (0)