From 47ddd3b911f5c85d8fbfc08eda530dac98f67dc9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Dec 2023 10:15:15 +0100 Subject: [PATCH] Do not generalize template types in `@template-covariant` --- src/Analyser/MutatingScope.php | 12 ++++---- src/Reflection/ResolvedFunctionVariant.php | 12 ++++---- tests/PHPStan/Analyser/data/bug-7078.php | 2 +- .../data/generics-do-not-generalize.php | 29 +++++++++++++++++++ 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 8bef830887..f992b20b86 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5063,11 +5063,13 @@ private function exactInstantiation(New_ $node, string $className): ?Type return $type->getBound(); } - $isArrayKey = $type->getBound()->describe(VerbosityLevel::precise()) === '(int|string)'; - if ($newType->isScalar()->yes() && !$type->getVariance()->covariant() && $isArrayKey) { - $newType = $newType->generalize(GeneralizePrecision::templateArgument()); - } elseif ($newType->isConstantValue()->yes() && (!$type->getBound()->isScalar()->yes() || $isArrayKey)) { - $newType = $newType->generalize(GeneralizePrecision::templateArgument()); + if (!$type->getVariance()->covariant()) { + $isArrayKey = $type->getBound()->describe(VerbosityLevel::precise()) === '(int|string)'; + if ($newType->isScalar()->yes() && $isArrayKey) { + $newType = $newType->generalize(GeneralizePrecision::templateArgument()); + } elseif ($newType->isConstantValue()->yes() && (!$type->getBound()->isScalar()->yes() || $isArrayKey)) { + $newType = $newType->generalize(GeneralizePrecision::templateArgument()); + } } return $newType; diff --git a/src/Reflection/ResolvedFunctionVariant.php b/src/Reflection/ResolvedFunctionVariant.php index cb026a8cb7..1616122192 100644 --- a/src/Reflection/ResolvedFunctionVariant.php +++ b/src/Reflection/ResolvedFunctionVariant.php @@ -184,11 +184,13 @@ private function resolveResolvableTemplateTypes(Type $type, TemplateTypeVariance return $traverse($type); } - $isArrayKey = $type->getBound()->describe(VerbosityLevel::precise()) === '(int|string)'; - if ($newType->isScalar()->yes() && !$type->getVariance()->covariant() && $isArrayKey) { - $newType = $newType->generalize(GeneralizePrecision::templateArgument()); - } elseif ($newType->isConstantValue()->yes() && (!$type->getBound()->isScalar()->yes() || $isArrayKey)) { - $newType = $newType->generalize(GeneralizePrecision::templateArgument()); + if (!$type->getVariance()->covariant()) { + $isArrayKey = $type->getBound()->describe(VerbosityLevel::precise()) === '(int|string)'; + if ($newType->isScalar()->yes() && $isArrayKey) { + $newType = $newType->generalize(GeneralizePrecision::templateArgument()); + } elseif ($newType->isConstantValue()->yes() && (!$type->getBound()->isScalar()->yes() || $isArrayKey)) { + $newType = $newType->generalize(GeneralizePrecision::templateArgument()); + } } $variance = TemplateTypeVariance::createInvariant(); diff --git a/tests/PHPStan/Analyser/data/bug-7078.php b/tests/PHPStan/Analyser/data/bug-7078.php index 11b688fc15..5287dcc7cf 100644 --- a/tests/PHPStan/Analyser/data/bug-7078.php +++ b/tests/PHPStan/Analyser/data/bug-7078.php @@ -33,5 +33,5 @@ public function get(TypeDefault ...$type); function (Param $p) { $result = $p->get(new TypeDefault(1), new TypeDefault('a')); - assertType('int|string', $result); + assertType('1|\'a\'', $result); }; diff --git a/tests/PHPStan/Analyser/data/generics-do-not-generalize.php b/tests/PHPStan/Analyser/data/generics-do-not-generalize.php index a472a8df11..d00b8b699a 100644 --- a/tests/PHPStan/Analyser/data/generics-do-not-generalize.php +++ b/tests/PHPStan/Analyser/data/generics-do-not-generalize.php @@ -117,3 +117,32 @@ function (): void { assertType('ArrayIterator', createArrayIterator($a)); }; + +/** @template T */ +class FooInvariant +{ + + /** @param T $p */ + public function __construct($p) + { + + } + +} + +/** @template-covariant T */ +class FooCovariant +{ + + /** @param T $p */ + public function __construct($p) + { + + } + +} + +function (): void { + assertType('GenericsDoNotGeneralize\\FooInvariant', new FooInvariant(1)); + assertType('GenericsDoNotGeneralize\\FooCovariant<1>', new FooCovariant(1)); +};