From 756af18c18f9f2c8156b6d398ed59e0ef9bf571e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 25 Feb 2021 11:46:52 +0100 Subject: [PATCH] TemplateTypeCheck - look for nested unsupported types --- src/Rules/Generics/TemplateTypeCheck.php | 27 +++++++++++-------- src/Type/Generic/TemplateTypeFactory.php | 5 ++-- .../InterfaceTemplateTypeRuleTest.php | 4 +++ .../Generics/data/interface-template.php | 12 +++++++++ 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index b0285a6f7b..702e72faad 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -11,6 +11,8 @@ use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\Type; +use PHPStan\Type\TypeTraverser; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function array_key_exists; @@ -101,18 +103,21 @@ public function check( $messages = array_merge($messages, $this->classCaseSensitivityCheck->checkClassNames($classNameNodePairs)); } - $bound = $templateTag->getBound(); - $boundClass = get_class($bound); - if ( - $boundClass === MixedType::class - || $boundClass === ObjectWithoutClassType::class - || $bound instanceof ObjectType - || $bound instanceof UnionType - ) { - continue; - } + TypeTraverser::map($templateTag->getBound(), static function (Type $type, callable $traverse) use (&$messages, $notSupportedBoundMessage, $templateTagName): Type { + $boundClass = get_class($type); + if ( + $boundClass === MixedType::class + || $boundClass === ObjectWithoutClassType::class + || $boundClass === ObjectType::class + || $type instanceof UnionType + ) { + return $traverse($type); + } + + $messages[] = RuleErrorBuilder::message(sprintf($notSupportedBoundMessage, $templateTagName, $type->describe(VerbosityLevel::typeOnly())))->build(); - $messages[] = RuleErrorBuilder::message(sprintf($notSupportedBoundMessage, $templateTagName, $boundType->describe(VerbosityLevel::typeOnly())))->build(); + return $type; + }); } return $messages; diff --git a/src/Type/Generic/TemplateTypeFactory.php b/src/Type/Generic/TemplateTypeFactory.php index 001a3841eb..c752999c72 100644 --- a/src/Type/Generic/TemplateTypeFactory.php +++ b/src/Type/Generic/TemplateTypeFactory.php @@ -21,11 +21,10 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou return new TemplateMixedType($scope, $strategy, $variance, $name); } - if ($bound instanceof ObjectType) { + $boundClass = get_class($bound); + if ($boundClass === ObjectType::class) { return new TemplateObjectType($scope, $strategy, $variance, $name, $bound->getClassName()); } - - $boundClass = get_class($bound); if ($boundClass === ObjectWithoutClassType::class) { return new TemplateObjectWithoutClassType($scope, $strategy, $variance, $name); } diff --git a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php index 4822752adf..7d3f194fab 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php @@ -41,6 +41,10 @@ public function testRule(): void 'PHPDoc tag @template for interface InterfaceTemplateType\Lorem cannot have existing type alias TypeAlias as its name.', 32, ], + [ + 'PHPDoc tag @template T for interface InterfaceTemplateType\UnionBound with bound type InterfaceTemplateType\NormalT is not supported.', + 44, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/data/interface-template.php b/tests/PHPStan/Rules/Generics/data/interface-template.php index e5a795b234..30f2c1d8d2 100644 --- a/tests/PHPStan/Rules/Generics/data/interface-template.php +++ b/tests/PHPStan/Rules/Generics/data/interface-template.php @@ -33,3 +33,15 @@ interface Lorem { } + +/** @template T */ +interface NormalT +{ + +} + +/** @template T of NormalT<\stdClass>|\stdClass */ +interface UnionBound +{ + +}