Skip to content

Commit

Permalink
Do not generalize template types in @template-covariant
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Dec 12, 2023
1 parent 7c83225 commit dc14cb6
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 11 deletions.
12 changes: 7 additions & 5 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 7 additions & 5 deletions src/Reflection/ResolvedFunctionVariant.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-7078.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
29 changes: 29 additions & 0 deletions tests/PHPStan/Analyser/data/generics-do-not-generalize.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,32 @@ function (): void {

assertType('ArrayIterator<int, string>', 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<int>', new FooInvariant(1));
assertType('GenericsDoNotGeneralize\\FooCovariant<1>', new FooCovariant(1));
};

0 comments on commit dc14cb6

Please sign in to comment.