diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index 838b4a95e1..c8151a5129 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -22,9 +22,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; -use PHPStan\Type\Type; use PHPStan\Type\TypeAliasResolver; -use PHPStan\Type\TypeTraverser; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function array_map; @@ -95,29 +93,24 @@ public function check( $messages = array_merge($messages, $this->classCaseSensitivityCheck->checkClassNames($classNameNodePairs)); } - TypeTraverser::map($templateTag->getBound(), static function (Type $type, callable $traverse) use (&$messages, $notSupportedBoundMessage, $templateTagName): Type { - $boundClass = get_class($type); - if ( - $boundClass === MixedType::class - || $boundClass === ConstantArrayType::class - || $boundClass === ArrayType::class - || $boundClass === StringType::class - || $boundClass === IntegerType::class - || $boundClass === FloatType::class - || $boundClass === BooleanType::class - || $boundClass === ObjectWithoutClassType::class - || $boundClass === ObjectType::class - || $boundClass === GenericObjectType::class - || $type instanceof UnionType - || $type instanceof TemplateType - ) { - return $traverse($type); - } - - $messages[] = RuleErrorBuilder::message(sprintf($notSupportedBoundMessage, $templateTagName, $type->describe(VerbosityLevel::typeOnly())))->build(); - - return $type; - }); + $boundType = $templateTag->getBound(); + $boundTypeClass = get_class($boundType); + if ( + $boundTypeClass !== MixedType::class + && $boundTypeClass !== ConstantArrayType::class + && $boundTypeClass !== ArrayType::class + && $boundTypeClass !== StringType::class + && $boundTypeClass !== IntegerType::class + && $boundTypeClass !== FloatType::class + && $boundTypeClass !== BooleanType::class + && $boundTypeClass !== ObjectWithoutClassType::class + && $boundTypeClass !== ObjectType::class + && $boundTypeClass !== GenericObjectType::class + && !$boundType instanceof UnionType + && !$boundType instanceof TemplateType + ) { + $messages[] = RuleErrorBuilder::message(sprintf($notSupportedBoundMessage, $templateTagName, $boundType->describe(VerbosityLevel::typeOnly())))->build(); + } $escapedTemplateTagName = SprintfHelper::escapeFormatString($templateTagName); $genericObjectErrors = $this->genericObjectTypeCheck->check( diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 1a0ccb53e3..550866b046 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -629,6 +629,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5817.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-column.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/template-null-bound.php'); } /** diff --git a/tests/PHPStan/Analyser/data/template-null-bound.php b/tests/PHPStan/Analyser/data/template-null-bound.php new file mode 100644 index 0000000000..66f1346914 --- /dev/null +++ b/tests/PHPStan/Analyser/data/template-null-bound.php @@ -0,0 +1,26 @@ +doFoo(null)); + assertType('int', $f->doFoo(1)); + assertType('int|null', $f->doFoo($i)); +}; diff --git a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php index 5e756829ad..22cb75b8b4 100644 --- a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php @@ -43,6 +43,10 @@ public function testRule(): void 'PHPDoc tag @template T for function FunctionTemplateType\resourceBound() with bound type resource is not supported.', 50, ], + [ + 'PHPDoc tag @template T for function FunctionTemplateType\nullNotSupported() with bound type null is not supported.', + 68, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/data/function-template.php b/tests/PHPStan/Rules/Generics/data/function-template.php index 11ed760df6..7124e4c138 100644 --- a/tests/PHPStan/Rules/Generics/data/function-template.php +++ b/tests/PHPStan/Rules/Generics/data/function-template.php @@ -63,3 +63,15 @@ function nakano() { } + +/** @template T of null */ +function nullNotSupported() +{ + +} + +/** @template T of ?int */ +function nullableUnionSupported() +{ + +}