Skip to content

Commit

Permalink
Ignore @template above a property
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 22, 2022
1 parent 74ae46c commit 53563e9
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 54 deletions.
2 changes: 1 addition & 1 deletion src/Analyser/NameScope.php
Expand Up @@ -107,7 +107,7 @@ public function resolveTemplateTypeName(string $name): ?Type

public function withTemplateTypeMap(TemplateTypeMap $map): self
{
if ($map->isEmpty()) {
if ($map->isEmpty() && $this->templateTypeMap->isEmpty()) {
return $this;
}

Expand Down
69 changes: 20 additions & 49 deletions src/Type/FileTypeMapper.php
Expand Up @@ -20,7 +20,6 @@
use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeFactory;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\Generic\TemplateTypeMap;
Expand Down Expand Up @@ -134,29 +133,6 @@ public function getResolvedPhpDoc(
private function createResolvedPhpDocBlock(string $phpDocKey, NameScope $nameScope, string $phpDocString, string $fileName): ResolvedPhpDocBlock
{
$phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString);
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
$templateTypeScope = $nameScope->getTemplateTypeScope();

if ($templateTypeScope !== null) {
$templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $templateTags));
$nameScope = $nameScope->withTemplateTypeMap(
new TemplateTypeMap(array_merge(
$nameScope->getTemplateTypeMap()->getTypes(),
$templateTypeMap->getTypes(),
)),
);
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
$templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $templateTags));
$nameScope = $nameScope->withTemplateTypeMap(
new TemplateTypeMap(array_merge(
$nameScope->getTemplateTypeMap()->getTypes(),
$templateTypeMap->getTypes(),
)),
);
} else {
$templateTypeMap = TemplateTypeMap::createEmpty();
}

if ($this->resolvedPhpDocBlockCacheCount >= 512) {
$this->resolvedPhpDocBlockCache = array_slice(
$this->resolvedPhpDocBlockCache,
Expand All @@ -168,12 +144,23 @@ private function createResolvedPhpDocBlock(string $phpDocKey, NameScope $nameSco
$this->resolvedPhpDocBlockCacheCount--;
}

$templateTypeMap = $nameScope->getTemplateTypeMap();
$phpDocTemplateTypes = [];
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
foreach (array_keys($templateTags) as $name) {
$templateType = $templateTypeMap->getType($name);
if ($templateType === null) {
continue;
}
$phpDocTemplateTypes[$name] = $templateType;
}

$this->resolvedPhpDocBlockCache[$phpDocKey] = ResolvedPhpDocBlock::create(
$phpDocNode,
$phpDocString,
$fileName,
$nameScope,
$templateTypeMap,
new TemplateTypeMap($phpDocTemplateTypes),
$templateTags,
$this->phpDocNodeResolver,
);
Expand All @@ -193,7 +180,7 @@ private function resolvePhpDocStringToDocNode(string $phpDocString): PhpDocNode
private function getNameScopeMap(string $fileName): array
{
if (!isset($this->memoryCache[$fileName])) {
$cacheKey = sprintf('%s-phpdocstring-v19-trait-detection-recursion', $fileName);
$cacheKey = sprintf('%s-phpdocstring-v20-template-tags', $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 Expand Up @@ -322,14 +309,15 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA

$className = $classStack[count($classStack) - 1] ?? null;
$functionName = $functionStack[count($functionStack) - 1] ?? null;
$resolvableTemplateTypes = ($className !== null && $lookForTrait === null) || $functionName !== null;

if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) {
$phpDocString = GetLastDocComment::forNode($node);
if ($phpDocString !== '') {
$typeMapStack[] = function () use ($namespace, $uses, $className, $functionName, $phpDocString, $typeMapStack, $resolvableTemplateTypes): TemplateTypeMap {
$typeMapStack[] = function () use ($namespace, $uses, $className, $functionName, $phpDocString, $typeMapStack): TemplateTypeMap {
$phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString);
$nameScope = new NameScope($namespace, $uses, $className, $functionName);
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;
$currentTypeMap = $typeMapCb !== null ? $typeMapCb() : null;
$nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap);
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
$templateTypeScope = $nameScope->getTemplateTypeScope();
if ($templateTypeScope === null) {
Expand All @@ -339,28 +327,11 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
$nameScope = $nameScope->withTemplateTypeMap($templateTypeMap);
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
$templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $templateTags));
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;

return (new TemplateTypeMap(array_merge(
$typeMapCb !== null ? $typeMapCb()->getTypes() : [],
return new TemplateTypeMap(array_merge(
$currentTypeMap !== null ? $currentTypeMap->getTypes() : [],
$templateTypeMap->getTypes(),
)))->map(static fn (string $name, Type $type): Type => TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($className, $resolvableTemplateTypes): Type {
if (!$type instanceof TemplateType) {
return $traverse($type);
}

if (!$resolvableTemplateTypes) {
return $traverse($type->toArgument());
}

$scope = $type->getScope();

if ($scope->getClassName() === null || $scope->getFunctionName() !== null || $scope->getClassName() !== $className) {
return $traverse($type->toArgument());
}

return $traverse($type);
}));
));
};
}
}
Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Expand Up @@ -541,6 +541,16 @@ public function testBug6501(): void
$this->assertNoErrors($errors);
}

public function testBug6114(): void
{
if (PHP_VERSION_ID < 80000) {
$this->markTestSkipped('Test requires PHP 8.0.');
}

$errors = $this->runAnalyse(__DIR__ . '/data/bug-6114.php');
$this->assertNoErrors($errors);
}

/**
* @param string[]|null $allAnalysedFiles
* @return Error[]
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -707,6 +707,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6488.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6624.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6672.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/property-template-tag.php');
}

/**
Expand Down
45 changes: 45 additions & 0 deletions tests/PHPStan/Analyser/data/bug-6114.php
@@ -0,0 +1,45 @@
<?php // lint >= 8.0

namespace Bug6114;

/**
* @template T
*/
interface Foo {
/**
* @return T
*/
public function bar(): mixed;
}

class HelloWorld
{
/**
* @template T
* @param T $value
* @return Foo<T>
*/
public function sayHello(mixed $value): Foo
{
return new
/**
* @template U
* @implements Foo<U>
*/ class($value) implements Foo {
/** @var U */
private mixed $value;

/**
* @param U $value
*/
public function __construct(mixed $value) {
$this->value = $value;
}

public function bar(): mixed
{
return $this->value;
}
};
}
}
8 changes: 4 additions & 4 deletions tests/PHPStan/Analyser/data/generics.php
Expand Up @@ -347,7 +347,7 @@ function varAnnotation($cb)
/** @var T */
$v = $cb();

assertType('T (function PHPStan\Generics\FunctionsAssertType\varAnnotation(), argument)', $v);
assertType('T (function PHPStan\Generics\FunctionsAssertType\varAnnotation(), parameter)', $v);

return $v;
}
Expand Down Expand Up @@ -384,7 +384,7 @@ public function g()
}
};

assertType('T (class PHPStan\Generics\FunctionsAssertType\C, argument)', $a->g());
assertType('T (class PHPStan\Generics\FunctionsAssertType\C, parameter)', $a->g());
}
}

Expand Down Expand Up @@ -952,7 +952,7 @@ public function doFoo($a)

/** @var T $b */
$b = doFoo();
assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doFoo(), argument)', $b);
assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doFoo(), parameter)', $b);
}

/**
Expand All @@ -965,7 +965,7 @@ public function doBar($a)

/** @var T $b */
$b = doFoo();
assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doBar(), argument)', $b);
assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doBar(), parameter)', $b);
}

}
Expand Down
21 changes: 21 additions & 0 deletions tests/PHPStan/Analyser/data/property-template-tag.php
@@ -0,0 +1,21 @@
<?php

namespace PropertyTemplateTag;

use function PHPStan\Testing\assertType;

class BaseObject { }

class ObjectDatabase
{

/** Array of class, then key, then value to object array
* @template T of BaseObject
* @var array<class-string<T>, array<string, array<string, array<string, T>>>> */
private array $objectsByKey = array();

public function LoadObjectsByKey() : void
{
assertType('array<class-string<PropertyTemplateTag\T>, array<string, array<string, array<string, PropertyTemplateTag\T>>>>', $this->objectsByKey);
}
}

0 comments on commit 53563e9

Please sign in to comment.