Skip to content

Commit

Permalink
Bleeding edge - GenericAncestorsCheck looks for unresolvable types
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Aug 25, 2024
1 parent e5600f1 commit 2bb5282
Show file tree
Hide file tree
Showing 11 changed files with 61 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,7 @@ services:
arguments:
checkGenericClassInNonGenericObjectType: %checkGenericClassInNonGenericObjectType%
skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses%
absentTypeChecks: %featureToggles.absentTypeChecks%

-
class: PHPStan\Rules\Generics\GenericObjectTypeCheck
Expand Down
2 changes: 2 additions & 0 deletions src/Rules/Generics/ClassAncestorsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function processNode(Node $node, Scope $scope): array
$originalNode->extends !== null ? [$originalNode->extends] : [],
array_map(static fn (ExtendsTag $tag): Type => $tag->getType(), $classReflection->getExtendsTags()),
sprintf('Class %s @extends tag contains incompatible type %%s.', $escapedClassName),
sprintf('Class %s @extends tag contains unresolvable type.', $className),
sprintf('Class %s has @extends tag, but does not extend any class.', $escapedClassName),
sprintf('The @extends tag of class %s describes %%s but the class extends %%s.', $escapedClassName),
'PHPDoc tag @extends contains generic type %s but %s %s is not generic.',
Expand All @@ -65,6 +66,7 @@ public function processNode(Node $node, Scope $scope): array
$originalNode->implements,
array_map(static fn (ImplementsTag $tag): Type => $tag->getType(), $classReflection->getImplementsTags()),
sprintf('Class %s @implements tag contains incompatible type %%s.', $escapedClassName),
sprintf('Class %s @implements tag contains unresolvable type.', $className),
sprintf('Class %s has @implements tag, but does not implement any interface.', $escapedClassName),
sprintf('The @implements tag of class %s describes %%s but the class implements: %%s', $escapedClassName),
'PHPDoc tag @implements contains generic type %s but %s %s is not generic.',
Expand Down
2 changes: 2 additions & 0 deletions src/Rules/Generics/EnumAncestorsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public function processNode(Node $node, Scope $scope): array
[],
array_map(static fn (ExtendsTag $tag): Type => $tag->getType(), $classReflection->getExtendsTags()),
sprintf('Enum %s @extends tag contains incompatible type %%s.', $escapedEnumName),
sprintf('Enum %s @extends tag contains unresolvable type.', $enumName),
sprintf('Enum %s has @extends tag, but cannot extend anything.', $escapedEnumName),
'',
'',
Expand All @@ -63,6 +64,7 @@ public function processNode(Node $node, Scope $scope): array
$originalNode->implements,
array_map(static fn (ImplementsTag $tag): Type => $tag->getType(), $classReflection->getImplementsTags()),
sprintf('Enum %s @implements tag contains incompatible type %%s.', $escapedEnumName),
sprintf('Enum %s @implements tag contains unresolvable type.', $enumName),
sprintf('Enum %s has @implements tag, but does not implement any interface.', $escapedEnumName),
sprintf('The @implements tag of eunm %s describes %%s but the enum implements: %%s', $escapedEnumName),
'PHPDoc tag @implements contains generic type %s but %s %s is not generic.',
Expand Down
12 changes: 12 additions & 0 deletions src/Rules/Generics/GenericAncestorsCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PhpParser\Node\Name;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateTypeVariance;
Expand All @@ -31,8 +32,10 @@ public function __construct(
private ReflectionProvider $reflectionProvider,
private GenericObjectTypeCheck $genericObjectTypeCheck,
private VarianceCheck $varianceCheck,
private UnresolvableTypeHelper $unresolvableTypeHelper,
private bool $checkGenericClassInNonGenericObjectType,
private array $skipCheckGenericClasses,
private bool $absentTypeChecks,
)
{
}
Expand All @@ -46,6 +49,7 @@ public function check(
array $nameNodes,
array $ancestorTypes,
string $incompatibleTypeMessage,
string $unresolvableTypeMessage,
string $noNamesMessage,
string $noRelatedNameMessage,
string $classNotGenericMessage,
Expand Down Expand Up @@ -99,6 +103,14 @@ public function check(
);
$messages = array_merge($messages, $genericObjectTypeCheckMessages);

if ($this->absentTypeChecks) {
if ($this->unresolvableTypeHelper->containsUnresolvableType($ancestorType)) {
$messages[] = RuleErrorBuilder::message($unresolvableTypeMessage)
->identifier('generics.unresolvable')
->build();
}
}

foreach ($ancestorType->getReferencedClasses() as $referencedClass) {
if ($this->reflectionProvider->hasClass($referencedClass)) {
continue;
Expand Down
2 changes: 2 additions & 0 deletions src/Rules/Generics/InterfaceAncestorsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public function processNode(Node $node, Scope $scope): array
$originalNode->extends,
array_map(static fn (ExtendsTag $tag): Type => $tag->getType(), $classReflection->getExtendsTags()),
sprintf('Interface %s @extends tag contains incompatible type %%s.', $escapedInterfaceName),
sprintf('Interface %s @extends tag contains unresolvable type.', $interfaceName),
sprintf('Interface %s has @extends tag, but does not extend any interface.', $escapedInterfaceName),
sprintf('The @extends tag of interface %s describes %%s but the interface extends: %%s', $escapedInterfaceName),
'PHPDoc tag @extends contains generic type %s but %s %s is not generic.',
Expand All @@ -63,6 +64,7 @@ public function processNode(Node $node, Scope $scope): array
[],
array_map(static fn (ImplementsTag $tag): Type => $tag->getType(), $classReflection->getImplementsTags()),
sprintf('Interface %s @implements tag contains incompatible type %%s.', $escapedInterfaceName),
sprintf('Interface %s @implements tag contains unresolvable type.', $interfaceName),
sprintf('Interface %s has @implements tag, but can not implement any interface, must extend from it.', $escapedInterfaceName),
'',
'',
Expand Down
1 change: 1 addition & 0 deletions src/Rules/Generics/UsedTraitsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public function processNode(Node $node, Scope $scope): array
$node->traits,
array_map(static fn (UsesTag $tag): Type => $tag->getType(), $useTags),
sprintf('%s @use tag contains incompatible type %%s.', ucfirst($description)),
sprintf('%s @use tag contains unresolvable type.', ucfirst($description)),
sprintf('%s has @use tag, but does not use any trait.', ucfirst($description)),
sprintf('The @use tag of %s describes %%s but the %s uses %%s.', $description, $typeDescription),
'PHPDoc tag @use contains generic type %s but %s %s is not generic.',
Expand Down
13 changes: 13 additions & 0 deletions tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\Generics;

use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

Expand All @@ -18,8 +19,10 @@ protected function getRule(): Rule
$this->createReflectionProvider(),
new GenericObjectTypeCheck(),
new VarianceCheck(true, true),
new UnresolvableTypeHelper(),
true,
[],
true,
),
new CrossCheckInterfacesHelper(),
);
Expand Down Expand Up @@ -269,4 +272,14 @@ public function testBug8473(): void
$this->analyse([__DIR__ . '/data/bug-8473.php'], []);
}

public function testBug11552(): void
{
$this->analyse([__DIR__ . '/data/bug-11552.php'], [
[
'Class Bug11552\SomeResult @extends tag contains unresolvable type.',
17,
],
]);
}

}
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\Generics;

use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;
Expand All @@ -19,8 +20,10 @@ protected function getRule(): Rule
$this->createReflectionProvider(),
new GenericObjectTypeCheck(),
new VarianceCheck(true, true),
new UnresolvableTypeHelper(),
true,
[],
true,
),
new CrossCheckInterfacesHelper(),
);
Expand Down
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\Generics;

use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

Expand All @@ -18,8 +19,10 @@ protected function getRule(): Rule
$this->createReflectionProvider(),
new GenericObjectTypeCheck(),
new VarianceCheck(true, true),
new UnresolvableTypeHelper(),
true,
[],
true,
),
new CrossCheckInterfacesHelper(),
);
Expand Down
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\Generics;

use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\FileTypeMapper;
Expand All @@ -20,8 +21,10 @@ protected function getRule(): Rule
$this->createReflectionProvider(),
new GenericObjectTypeCheck(),
new VarianceCheck(true, true),
new UnresolvableTypeHelper(),
true,
[],
true,
),
);
}
Expand Down
19 changes: 19 additions & 0 deletions tests/PHPStan/Rules/Generics/data/bug-11552.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Bug11552;

/**
* @template TSuccess
* @template TError
*/
class Result
{

}

/**
* @extends Result<void, SomeResult::*>
*/
class SomeResult extends Result {

}

0 comments on commit 2bb5282

Please sign in to comment.