Skip to content

Commit 2bb5282

Browse files
committed
Bleeding edge - GenericAncestorsCheck looks for unresolvable types
1 parent e5600f1 commit 2bb5282

11 files changed

+61
-0
lines changed

conf/config.neon

+1
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,7 @@ services:
994994
arguments:
995995
checkGenericClassInNonGenericObjectType: %checkGenericClassInNonGenericObjectType%
996996
skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses%
997+
absentTypeChecks: %featureToggles.absentTypeChecks%
997998

998999
-
9991000
class: PHPStan\Rules\Generics\GenericObjectTypeCheck

src/Rules/Generics/ClassAncestorsRule.php

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public function processNode(Node $node, Scope $scope): array
4949
$originalNode->extends !== null ? [$originalNode->extends] : [],
5050
array_map(static fn (ExtendsTag $tag): Type => $tag->getType(), $classReflection->getExtendsTags()),
5151
sprintf('Class %s @extends tag contains incompatible type %%s.', $escapedClassName),
52+
sprintf('Class %s @extends tag contains unresolvable type.', $className),
5253
sprintf('Class %s has @extends tag, but does not extend any class.', $escapedClassName),
5354
sprintf('The @extends tag of class %s describes %%s but the class extends %%s.', $escapedClassName),
5455
'PHPDoc tag @extends contains generic type %s but %s %s is not generic.',
@@ -65,6 +66,7 @@ public function processNode(Node $node, Scope $scope): array
6566
$originalNode->implements,
6667
array_map(static fn (ImplementsTag $tag): Type => $tag->getType(), $classReflection->getImplementsTags()),
6768
sprintf('Class %s @implements tag contains incompatible type %%s.', $escapedClassName),
69+
sprintf('Class %s @implements tag contains unresolvable type.', $className),
6870
sprintf('Class %s has @implements tag, but does not implement any interface.', $escapedClassName),
6971
sprintf('The @implements tag of class %s describes %%s but the class implements: %%s', $escapedClassName),
7072
'PHPDoc tag @implements contains generic type %s but %s %s is not generic.',

src/Rules/Generics/EnumAncestorsRule.php

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public function processNode(Node $node, Scope $scope): array
4747
[],
4848
array_map(static fn (ExtendsTag $tag): Type => $tag->getType(), $classReflection->getExtendsTags()),
4949
sprintf('Enum %s @extends tag contains incompatible type %%s.', $escapedEnumName),
50+
sprintf('Enum %s @extends tag contains unresolvable type.', $enumName),
5051
sprintf('Enum %s has @extends tag, but cannot extend anything.', $escapedEnumName),
5152
'',
5253
'',
@@ -63,6 +64,7 @@ public function processNode(Node $node, Scope $scope): array
6364
$originalNode->implements,
6465
array_map(static fn (ImplementsTag $tag): Type => $tag->getType(), $classReflection->getImplementsTags()),
6566
sprintf('Enum %s @implements tag contains incompatible type %%s.', $escapedEnumName),
67+
sprintf('Enum %s @implements tag contains unresolvable type.', $enumName),
6668
sprintf('Enum %s has @implements tag, but does not implement any interface.', $escapedEnumName),
6769
sprintf('The @implements tag of eunm %s describes %%s but the enum implements: %%s', $escapedEnumName),
6870
'PHPDoc tag @implements contains generic type %s but %s %s is not generic.',

src/Rules/Generics/GenericAncestorsCheck.php

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PhpParser\Node\Name;
77
use PHPStan\Reflection\ReflectionProvider;
88
use PHPStan\Rules\IdentifierRuleError;
9+
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
910
use PHPStan\Rules\RuleErrorBuilder;
1011
use PHPStan\Type\Generic\GenericObjectType;
1112
use PHPStan\Type\Generic\TemplateTypeVariance;
@@ -31,8 +32,10 @@ public function __construct(
3132
private ReflectionProvider $reflectionProvider,
3233
private GenericObjectTypeCheck $genericObjectTypeCheck,
3334
private VarianceCheck $varianceCheck,
35+
private UnresolvableTypeHelper $unresolvableTypeHelper,
3436
private bool $checkGenericClassInNonGenericObjectType,
3537
private array $skipCheckGenericClasses,
38+
private bool $absentTypeChecks,
3639
)
3740
{
3841
}
@@ -46,6 +49,7 @@ public function check(
4649
array $nameNodes,
4750
array $ancestorTypes,
4851
string $incompatibleTypeMessage,
52+
string $unresolvableTypeMessage,
4953
string $noNamesMessage,
5054
string $noRelatedNameMessage,
5155
string $classNotGenericMessage,
@@ -99,6 +103,14 @@ public function check(
99103
);
100104
$messages = array_merge($messages, $genericObjectTypeCheckMessages);
101105

106+
if ($this->absentTypeChecks) {
107+
if ($this->unresolvableTypeHelper->containsUnresolvableType($ancestorType)) {
108+
$messages[] = RuleErrorBuilder::message($unresolvableTypeMessage)
109+
->identifier('generics.unresolvable')
110+
->build();
111+
}
112+
}
113+
102114
foreach ($ancestorType->getReferencedClasses() as $referencedClass) {
103115
if ($this->reflectionProvider->hasClass($referencedClass)) {
104116
continue;

src/Rules/Generics/InterfaceAncestorsRule.php

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public function processNode(Node $node, Scope $scope): array
4747
$originalNode->extends,
4848
array_map(static fn (ExtendsTag $tag): Type => $tag->getType(), $classReflection->getExtendsTags()),
4949
sprintf('Interface %s @extends tag contains incompatible type %%s.', $escapedInterfaceName),
50+
sprintf('Interface %s @extends tag contains unresolvable type.', $interfaceName),
5051
sprintf('Interface %s has @extends tag, but does not extend any interface.', $escapedInterfaceName),
5152
sprintf('The @extends tag of interface %s describes %%s but the interface extends: %%s', $escapedInterfaceName),
5253
'PHPDoc tag @extends contains generic type %s but %s %s is not generic.',
@@ -63,6 +64,7 @@ public function processNode(Node $node, Scope $scope): array
6364
[],
6465
array_map(static fn (ImplementsTag $tag): Type => $tag->getType(), $classReflection->getImplementsTags()),
6566
sprintf('Interface %s @implements tag contains incompatible type %%s.', $escapedInterfaceName),
67+
sprintf('Interface %s @implements tag contains unresolvable type.', $interfaceName),
6668
sprintf('Interface %s has @implements tag, but can not implement any interface, must extend from it.', $escapedInterfaceName),
6769
'',
6870
'',

src/Rules/Generics/UsedTraitsRule.php

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public function processNode(Node $node, Scope $scope): array
6868
$node->traits,
6969
array_map(static fn (UsesTag $tag): Type => $tag->getType(), $useTags),
7070
sprintf('%s @use tag contains incompatible type %%s.', ucfirst($description)),
71+
sprintf('%s @use tag contains unresolvable type.', ucfirst($description)),
7172
sprintf('%s has @use tag, but does not use any trait.', ucfirst($description)),
7273
sprintf('The @use tag of %s describes %%s but the %s uses %%s.', $description, $typeDescription),
7374
'PHPDoc tag @use contains generic type %s but %s %s is not generic.',

tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php

+13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Generics;
44

5+
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Testing\RuleTestCase;
78

@@ -18,8 +19,10 @@ protected function getRule(): Rule
1819
$this->createReflectionProvider(),
1920
new GenericObjectTypeCheck(),
2021
new VarianceCheck(true, true),
22+
new UnresolvableTypeHelper(),
2123
true,
2224
[],
25+
true,
2326
),
2427
new CrossCheckInterfacesHelper(),
2528
);
@@ -269,4 +272,14 @@ public function testBug8473(): void
269272
$this->analyse([__DIR__ . '/data/bug-8473.php'], []);
270273
}
271274

275+
public function testBug11552(): void
276+
{
277+
$this->analyse([__DIR__ . '/data/bug-11552.php'], [
278+
[
279+
'Class Bug11552\SomeResult @extends tag contains unresolvable type.',
280+
17,
281+
],
282+
]);
283+
}
284+
272285
}

tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Generics;
44

5+
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Testing\RuleTestCase;
78
use const PHP_VERSION_ID;
@@ -19,8 +20,10 @@ protected function getRule(): Rule
1920
$this->createReflectionProvider(),
2021
new GenericObjectTypeCheck(),
2122
new VarianceCheck(true, true),
23+
new UnresolvableTypeHelper(),
2224
true,
2325
[],
26+
true,
2427
),
2528
new CrossCheckInterfacesHelper(),
2629
);

tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Generics;
44

5+
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Testing\RuleTestCase;
78

@@ -18,8 +19,10 @@ protected function getRule(): Rule
1819
$this->createReflectionProvider(),
1920
new GenericObjectTypeCheck(),
2021
new VarianceCheck(true, true),
22+
new UnresolvableTypeHelper(),
2123
true,
2224
[],
25+
true,
2326
),
2427
new CrossCheckInterfacesHelper(),
2528
);

tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Generics;
44

5+
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Testing\RuleTestCase;
78
use PHPStan\Type\FileTypeMapper;
@@ -20,8 +21,10 @@ protected function getRule(): Rule
2021
$this->createReflectionProvider(),
2122
new GenericObjectTypeCheck(),
2223
new VarianceCheck(true, true),
24+
new UnresolvableTypeHelper(),
2325
true,
2426
[],
27+
true,
2528
),
2629
);
2730
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Bug11552;
4+
5+
/**
6+
* @template TSuccess
7+
* @template TError
8+
*/
9+
class Result
10+
{
11+
12+
}
13+
14+
/**
15+
* @extends Result<void, SomeResult::*>
16+
*/
17+
class SomeResult extends Result {
18+
19+
}

0 commit comments

Comments
 (0)