Skip to content

Commit

Permalink
Experiment - do not generalize template types, except when in Generic…
Browse files Browse the repository at this point in the history
…ObjectType
  • Loading branch information
ondrejmirtes committed Dec 10, 2023
1 parent fb76c9f commit 1a00838
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 11 deletions.
25 changes: 22 additions & 3 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -5013,10 +5013,26 @@ private function exactInstantiation(New_ $node, string $className): ?Type
);

if ($this->explicitMixedInUnknownGenericNew) {
return new GenericObjectType(
$resolvedTemplateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap();
return TypeTraverser::map(new GenericObjectType(
$resolvedClassName,
$classReflection->typeMapToList($parametersAcceptor->getResolvedTemplateTypeMap()),
);
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()),
), static function (Type $type, callable $traverse) use ($resolvedTemplateTypeMap): Type {
if ($type instanceof TemplateType && !$type->isArgument()) {
$newType = $resolvedTemplateTypeMap->getType($type->getName());
if ($newType === null || $newType instanceof ErrorType) {
return $traverse($type);
}

if ($newType->isScalar()->yes()) {
$newType = $newType->generalize(GeneralizePrecision::templateArgument());
}

return $newType;
}

return $traverse($type);
});
}

$resolvedPhpDoc = $classReflection->getResolvedPhpDoc();
Expand All @@ -5029,6 +5045,9 @@ private function exactInstantiation(New_ $node, string $className): ?Type
foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
$templateType = $typeMap->getType($tag->getName());
if ($templateType !== null) {
if ($templateType->isScalar()->yes()) {
$templateType = $templateType->generalize(GeneralizePrecision::templateArgument());
}
$list[] = $templateType;
continue;
}
Expand Down
48 changes: 47 additions & 1 deletion src/Reflection/ResolvedFunctionVariant.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use PHPStan\Reflection\Php\DummyParameterWithPhpDocs;
use PHPStan\Type\ConditionalTypeForParameter;
use PHPStan\Type\ErrorType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\Generic\TemplateTypeMap;
Expand Down Expand Up @@ -173,7 +175,51 @@ private function resolveResolvableTemplateTypes(Type $type, TemplateTypeVariance
{
$references = $type->getReferencedTemplateTypes($positionVariance);

return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($references): Type {
$objectCb = function (Type $type, callable $traverse) use ($references): Type {
if ($type instanceof TemplateType && !$type->isArgument()) {
$newType = $this->resolvedTemplateTypeMap->getType($type->getName());
if ($newType === null || $newType instanceof ErrorType) {
return $traverse($type);
}

if ($newType->isScalar()->yes()) {
$newType = $newType->generalize(GeneralizePrecision::templateArgument());
}

$variance = TemplateTypeVariance::createInvariant();
foreach ($references as $reference) {
// this uses identity to distinguish between different occurrences of the same template type
// see https://github.com/phpstan/phpstan-src/pull/2485#discussion_r1328555397 for details
if ($reference->getType() === $type) {
$variance = $reference->getPositionVariance();
break;
}
}

$callSiteVariance = $this->callSiteVarianceMap->getVariance($type->getName());
if ($callSiteVariance === null || $callSiteVariance->invariant()) {
return $newType;
}

if (!$callSiteVariance->covariant() && $variance->covariant()) {
return $traverse($type->getBound());
}

if (!$callSiteVariance->contravariant() && $variance->contravariant()) {
return new NonAcceptingNeverType();
}

return $newType;
}

return $traverse($type);
};

return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($references, $objectCb): Type {
if ($type instanceof GenericObjectType) {
return TypeTraverser::map($type, $objectCb);
}

if ($type instanceof TemplateType && !$type->isArgument()) {
$newType = $this->resolvedTemplateTypeMap->getType($type->getName());
if ($newType === null || $newType instanceof ErrorType) {
Expand Down
7 changes: 0 additions & 7 deletions src/Type/Generic/TemplateTypeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\TrinaryLogic;
use PHPStan\Type\AcceptsResult;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
Expand Down Expand Up @@ -265,12 +264,6 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
TemplateTypeVariance::createStatic(),
));
if ($resolvedBound->isSuperTypeOf($receivedType)->yes()) {
if ($this->shouldGeneralizeInferredType()) {
$generalizedType = $receivedType->generalize(GeneralizePrecision::templateArgument());
if ($resolvedBound->isSuperTypeOf($generalizedType)->yes()) {
$receivedType = $generalizedType;
}
}
return (new TemplateTypeMap([
$this->name => $receivedType,
]))->union($map);
Expand Down

0 comments on commit 1a00838

Please sign in to comment.