Skip to content

Commit

Permalink
Template tag without bound is explicit mixed
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 27, 2022
1 parent dbbb721 commit 0808a89
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/PhpDoc/PhpDocNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ public function resolveTemplateTags(PhpDocNode $phpDocNode, NameScope $nameScope

$resolved[$valueNode->name] = new TemplateTag(
$valueNode->name,
$valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope->unsetTemplateType($valueNode->name)) : new MixedType(),
$valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope->unsetTemplateType($valueNode->name)) : new MixedType(true),
$variance,
);
$resolvedPrefix[$valueNode->name] = $prefix;
Expand Down
32 changes: 27 additions & 5 deletions src/Reflection/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ public function getParentClass(): ?ClassReflection
if ($this->isGeneric()) {
$extendedType = TemplateTypeHelper::resolveTemplateTypes(
$extendedType,
$this->getActiveTemplateTypeMap(),
$this->getPossiblyIncompleteActiveTemplateTypeMap(),
true,
);
}

Expand All @@ -195,7 +196,7 @@ public function getParentClass(): ?ClassReflection
$parentReflection = $this->reflectionProvider->getClass($parentClass->getName());
if ($parentReflection->isGeneric()) {
return $parentReflection->withTypes(
array_values($parentReflection->getTemplateTypeMap()->resolveToBounds()->getTypes()),
array_values($parentReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
);
}

Expand Down Expand Up @@ -224,7 +225,7 @@ public function getDisplayName(bool $withTemplateTypes = true): string
return $name;
}

return $name . '<' . implode(',', array_map(static fn (Type $type): string => $type->describe(VerbosityLevel::typeOnly()), $this->resolvedTemplateTypeMap->getTypes())) . '>';
return $name . '<' . implode(',', array_map(static fn (Type $type): string => $type->describe(VerbosityLevel::typeOnly()), $this->getActiveTemplateTypeMap()->getTypes())) . '>';
}

public function getCacheKey(): string
Expand Down Expand Up @@ -728,7 +729,8 @@ public function getImmediateInterfaces(): array
if ($this->isGeneric()) {
$implementedType = TemplateTypeHelper::resolveTemplateTypes(
$implementedType,
$this->getActiveTemplateTypeMap(),
$this->getPossiblyIncompleteActiveTemplateTypeMap(),
true,
);
}

Expand All @@ -743,7 +745,7 @@ public function getImmediateInterfaces(): array

if ($immediateInterface->isGeneric()) {
$immediateInterfaces[$immediateInterface->getName()] = $immediateInterface->withTypes(
array_values($immediateInterface->getTemplateTypeMap()->resolveToBounds()->getTypes()),
array_values($immediateInterface->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
);
continue;
}
Expand Down Expand Up @@ -1057,6 +1059,26 @@ public function getTemplateTypeMap(): TemplateTypeMap
}

public function getActiveTemplateTypeMap(): TemplateTypeMap
{
$resolved = $this->resolvedTemplateTypeMap;
if ($resolved !== null) {
$templateTypeMap = $this->getTemplateTypeMap();
return $resolved->map(static function (string $name, Type $type) use ($templateTypeMap): Type {
if ($type instanceof ErrorType) {
$templateType = $templateTypeMap->getType($name);
if ($templateType !== null) {
return TemplateTypeHelper::resolveToBounds($templateType);
}
}

return $type;
});
}

return $this->getTemplateTypeMap();
}

public function getPossiblyIncompleteActiveTemplateTypeMap(): TemplateTypeMap
{
return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Type/FileTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ private function resolvePhpDocStringToDocNode(string $phpDocString): PhpDocNode
private function getNameScopeMap(string $fileName): array
{
if (!isset($this->memoryCache[$fileName])) {
$cacheKey = sprintf('%s-phpdocstring-v20-template-tags', $fileName);
$cacheKey = sprintf('%s-phpdocstring-v21-explicit-mixed', $fileName);
$variableCacheKey = sprintf('%s-%s', implode(',', array_map(static fn (array $file): string => sprintf('%s-%d', $file['filename'], $file['modifiedTime']), $this->getCachedDependentFilesWithTimestamps($fileName))), $this->phpVersion->getVersionString());
$map = $this->cache->load($cacheKey, $variableCacheKey);

Expand Down
6 changes: 3 additions & 3 deletions src/Type/Generic/TemplateTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ class TemplateTypeHelper
/**
* Replaces template types with standin types
*/
public static function resolveTemplateTypes(Type $type, TemplateTypeMap $standins): Type
public static function resolveTemplateTypes(Type $type, TemplateTypeMap $standins, bool $keepErrorTypes = false): Type
{
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($standins): Type {
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($standins, $keepErrorTypes): Type {
if ($type instanceof TemplateType && !$type->isArgument()) {
$newType = $standins->getType($type->getName());
if ($newType === null) {
return $traverse($type);
}

if ($newType instanceof ErrorType) {
if ($newType instanceof ErrorType && !$keepErrorTypes) {
return $traverse($type->getBound());
}

Expand Down
15 changes: 6 additions & 9 deletions src/Type/Generic/TemplateTypeMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace PHPStan\Type\Generic;

use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
Expand All @@ -16,6 +15,8 @@ class TemplateTypeMap

private static ?TemplateTypeMap $empty = null;

private ?TemplateTypeMap $resolvedToBounds = null;

/**
* @api
* @param array<string, Type> $types
Expand Down Expand Up @@ -204,14 +205,10 @@ public function map(callable $cb): self

public function resolveToBounds(): self
{
return $this->map(static function (string $name, Type $type): Type {
$type = TemplateTypeHelper::resolveToBounds($type);
if ($type instanceof MixedType && $type->isExplicitMixed()) {
return new MixedType(false);
}

return $type;
});
if ($this->resolvedToBounds !== null) {
return $this->resolvedToBounds;
}
return $this->resolvedToBounds = $this->map(static fn (string $name, Type $type): Type => TemplateTypeHelper::resolveToBounds($type));
}

/**
Expand Down
34 changes: 27 additions & 7 deletions src/Type/GenericTypeVariableResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace PHPStan\Type;

use PHPStan\Type\Generic\TemplateTypeHelper;

/** @api */
class GenericTypeVariableResolver
{
Expand All @@ -12,19 +14,37 @@ public static function getType(
string $typeVariableName,
): ?Type
{
$ancestor = $type->getAncestorWithClassName($genericClassName);
if ($ancestor === null) {
$classReflection = $type->getClassReflection();
if ($classReflection === null) {
return null;
}

$classReflection = $ancestor->getClassReflection();
if ($classReflection === null) {
$ancestorClassReflection = $classReflection->getAncestorWithClassName($genericClassName);
if ($ancestorClassReflection === null) {
return null;
}

$templateTypeMap = $classReflection->getActiveTemplateTypeMap();
$activeTemplateTypeMap = $ancestorClassReflection->getPossiblyIncompleteActiveTemplateTypeMap();

// todo if type is not defined, return the bound
// in case of mixed bound, return implicit mixed

$type = $activeTemplateTypeMap->getType($typeVariableName);
if ($type instanceof ErrorType) {
$templateTypeMap = $ancestorClassReflection->getTemplateTypeMap();
$templateType = $templateTypeMap->getType($typeVariableName);
if ($templateType === null) {
return $type;
}

$bound = TemplateTypeHelper::resolveToBounds($templateType);
if ($bound instanceof MixedType && $bound->isExplicitMixed()) {
return new MixedType(false);
}

return $bound;
}

return $templateTypeMap->getType($typeVariableName);
return $type;
}

}
2 changes: 1 addition & 1 deletion src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@ public function getClassReflection(): ?ClassReflection

$classReflection = $reflectionProvider->getClass($this->className);
if ($classReflection->isGeneric()) {
return $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->resolveToBounds()->getTypes()));
return $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()));
}

return $classReflection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,9 @@ public function testBug6472(): void
$this->analyse([__DIR__ . '/data/bug-6472.php'], []);
}

public function testFilterIteratorChildClass(): void
{
$this->analyse([__DIR__ . '/data/filter-iterator-child-class.php'], []);
}

}
27 changes: 27 additions & 0 deletions tests/PHPStan/Rules/Methods/data/filter-iterator-child-class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types=1);

namespace FilterIteratorChild;

class ArchivableFilesFinder extends \FilterIterator
{

public function __construct()
{
parent::__construct(new \ArrayIterator([]));
}

public function accept(): bool
{
return true;
}
}

class ArchivableFilesFinderTest
{

public function doFoo(ArchivableFilesFinder $finder): void
{

}

}

0 comments on commit 0808a89

Please sign in to comment.