From 53563e9429f9559120aa8e043d9283c6b4c2d0a9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Feb 2022 07:39:32 +0100 Subject: [PATCH] Ignore `@template` above a property --- src/Analyser/NameScope.php | 2 +- src/Type/FileTypeMapper.php | 69 ++++++------------- .../Analyser/AnalyserIntegrationTest.php | 10 +++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-6114.php | 45 ++++++++++++ tests/PHPStan/Analyser/data/generics.php | 8 +-- .../Analyser/data/property-template-tag.php | 21 ++++++ 7 files changed, 102 insertions(+), 54 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-6114.php create mode 100644 tests/PHPStan/Analyser/data/property-template-tag.php diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index 4d43461d14..683732b28c 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -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; } diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index dc08aab1a7..a72aa670ee 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -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; @@ -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, @@ -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, ); @@ -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); @@ -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) { @@ -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); - })); + )); }; } } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 3de557c17b..23086365a1 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -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[] diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 3316b946b9..ec7b21f197 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -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'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-6114.php b/tests/PHPStan/Analyser/data/bug-6114.php new file mode 100644 index 0000000000..73b7c4f507 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-6114.php @@ -0,0 +1,45 @@ += 8.0 + +namespace Bug6114; + +/** + * @template T + */ +interface Foo { + /** + * @return T + */ + public function bar(): mixed; +} + +class HelloWorld +{ + /** + * @template T + * @param T $value + * @return Foo + */ + public function sayHello(mixed $value): Foo + { + return new + /** + * @template U + * @implements Foo + */ 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; + } + }; + } +} diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index 95a15b2598..75499a1412 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -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; } @@ -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()); } } @@ -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); } /** @@ -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); } } diff --git a/tests/PHPStan/Analyser/data/property-template-tag.php b/tests/PHPStan/Analyser/data/property-template-tag.php new file mode 100644 index 0000000000..11310ed98d --- /dev/null +++ b/tests/PHPStan/Analyser/data/property-template-tag.php @@ -0,0 +1,21 @@ +, array>>> */ + private array $objectsByKey = array(); + + public function LoadObjectsByKey() : void + { + assertType('array, array>>>', $this->objectsByKey); + } +}