Skip to content

Commit 53563e9

Browse files
committed
Ignore @template above a property
1 parent 74ae46c commit 53563e9

File tree

7 files changed

+102
-54
lines changed

7 files changed

+102
-54
lines changed

src/Analyser/NameScope.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public function resolveTemplateTypeName(string $name): ?Type
107107

108108
public function withTemplateTypeMap(TemplateTypeMap $map): self
109109
{
110-
if ($map->isEmpty()) {
110+
if ($map->isEmpty() && $this->templateTypeMap->isEmpty()) {
111111
return $this;
112112
}
113113

src/Type/FileTypeMapper.php

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
2121
use PHPStan\ShouldNotHappenException;
2222
use PHPStan\Type\Generic\GenericObjectType;
23-
use PHPStan\Type\Generic\TemplateType;
2423
use PHPStan\Type\Generic\TemplateTypeFactory;
2524
use PHPStan\Type\Generic\TemplateTypeHelper;
2625
use PHPStan\Type\Generic\TemplateTypeMap;
@@ -134,29 +133,6 @@ public function getResolvedPhpDoc(
134133
private function createResolvedPhpDocBlock(string $phpDocKey, NameScope $nameScope, string $phpDocString, string $fileName): ResolvedPhpDocBlock
135134
{
136135
$phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString);
137-
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
138-
$templateTypeScope = $nameScope->getTemplateTypeScope();
139-
140-
if ($templateTypeScope !== null) {
141-
$templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $templateTags));
142-
$nameScope = $nameScope->withTemplateTypeMap(
143-
new TemplateTypeMap(array_merge(
144-
$nameScope->getTemplateTypeMap()->getTypes(),
145-
$templateTypeMap->getTypes(),
146-
)),
147-
);
148-
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
149-
$templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $templateTags));
150-
$nameScope = $nameScope->withTemplateTypeMap(
151-
new TemplateTypeMap(array_merge(
152-
$nameScope->getTemplateTypeMap()->getTypes(),
153-
$templateTypeMap->getTypes(),
154-
)),
155-
);
156-
} else {
157-
$templateTypeMap = TemplateTypeMap::createEmpty();
158-
}
159-
160136
if ($this->resolvedPhpDocBlockCacheCount >= 512) {
161137
$this->resolvedPhpDocBlockCache = array_slice(
162138
$this->resolvedPhpDocBlockCache,
@@ -168,12 +144,23 @@ private function createResolvedPhpDocBlock(string $phpDocKey, NameScope $nameSco
168144
$this->resolvedPhpDocBlockCacheCount--;
169145
}
170146

147+
$templateTypeMap = $nameScope->getTemplateTypeMap();
148+
$phpDocTemplateTypes = [];
149+
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
150+
foreach (array_keys($templateTags) as $name) {
151+
$templateType = $templateTypeMap->getType($name);
152+
if ($templateType === null) {
153+
continue;
154+
}
155+
$phpDocTemplateTypes[$name] = $templateType;
156+
}
157+
171158
$this->resolvedPhpDocBlockCache[$phpDocKey] = ResolvedPhpDocBlock::create(
172159
$phpDocNode,
173160
$phpDocString,
174161
$fileName,
175162
$nameScope,
176-
$templateTypeMap,
163+
new TemplateTypeMap($phpDocTemplateTypes),
177164
$templateTags,
178165
$this->phpDocNodeResolver,
179166
);
@@ -193,7 +180,7 @@ private function resolvePhpDocStringToDocNode(string $phpDocString): PhpDocNode
193180
private function getNameScopeMap(string $fileName): array
194181
{
195182
if (!isset($this->memoryCache[$fileName])) {
196-
$cacheKey = sprintf('%s-phpdocstring-v19-trait-detection-recursion', $fileName);
183+
$cacheKey = sprintf('%s-phpdocstring-v20-template-tags', $fileName);
197184
$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());
198185
$map = $this->cache->load($cacheKey, $variableCacheKey);
199186

@@ -322,14 +309,15 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
322309

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

327313
if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) {
328314
$phpDocString = GetLastDocComment::forNode($node);
329315
if ($phpDocString !== '') {
330-
$typeMapStack[] = function () use ($namespace, $uses, $className, $functionName, $phpDocString, $typeMapStack, $resolvableTemplateTypes): TemplateTypeMap {
316+
$typeMapStack[] = function () use ($namespace, $uses, $className, $functionName, $phpDocString, $typeMapStack): TemplateTypeMap {
331317
$phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString);
332-
$nameScope = new NameScope($namespace, $uses, $className, $functionName);
318+
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;
319+
$currentTypeMap = $typeMapCb !== null ? $typeMapCb() : null;
320+
$nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap);
333321
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
334322
$templateTypeScope = $nameScope->getTemplateTypeScope();
335323
if ($templateTypeScope === null) {
@@ -339,28 +327,11 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
339327
$nameScope = $nameScope->withTemplateTypeMap($templateTypeMap);
340328
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
341329
$templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $templateTags));
342-
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;
343330

344-
return (new TemplateTypeMap(array_merge(
345-
$typeMapCb !== null ? $typeMapCb()->getTypes() : [],
331+
return new TemplateTypeMap(array_merge(
332+
$currentTypeMap !== null ? $currentTypeMap->getTypes() : [],
346333
$templateTypeMap->getTypes(),
347-
)))->map(static fn (string $name, Type $type): Type => TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($className, $resolvableTemplateTypes): Type {
348-
if (!$type instanceof TemplateType) {
349-
return $traverse($type);
350-
}
351-
352-
if (!$resolvableTemplateTypes) {
353-
return $traverse($type->toArgument());
354-
}
355-
356-
$scope = $type->getScope();
357-
358-
if ($scope->getClassName() === null || $scope->getFunctionName() !== null || $scope->getClassName() !== $className) {
359-
return $traverse($type->toArgument());
360-
}
361-
362-
return $traverse($type);
363-
}));
334+
));
364335
};
365336
}
366337
}

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,16 @@ public function testBug6501(): void
541541
$this->assertNoErrors($errors);
542542
}
543543

544+
public function testBug6114(): void
545+
{
546+
if (PHP_VERSION_ID < 80000) {
547+
$this->markTestSkipped('Test requires PHP 8.0.');
548+
}
549+
550+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-6114.php');
551+
$this->assertNoErrors($errors);
552+
}
553+
544554
/**
545555
* @param string[]|null $allAnalysedFiles
546556
* @return Error[]

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ public function dataFileAsserts(): iterable
707707
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6488.php');
708708
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6624.php');
709709
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6672.php');
710+
yield from $this->gatherAssertTypes(__DIR__ . '/data/property-template-tag.php');
710711
}
711712

712713
/**
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug6114;
4+
5+
/**
6+
* @template T
7+
*/
8+
interface Foo {
9+
/**
10+
* @return T
11+
*/
12+
public function bar(): mixed;
13+
}
14+
15+
class HelloWorld
16+
{
17+
/**
18+
* @template T
19+
* @param T $value
20+
* @return Foo<T>
21+
*/
22+
public function sayHello(mixed $value): Foo
23+
{
24+
return new
25+
/**
26+
* @template U
27+
* @implements Foo<U>
28+
*/ class($value) implements Foo {
29+
/** @var U */
30+
private mixed $value;
31+
32+
/**
33+
* @param U $value
34+
*/
35+
public function __construct(mixed $value) {
36+
$this->value = $value;
37+
}
38+
39+
public function bar(): mixed
40+
{
41+
return $this->value;
42+
}
43+
};
44+
}
45+
}

tests/PHPStan/Analyser/data/generics.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ function varAnnotation($cb)
347347
/** @var T */
348348
$v = $cb();
349349

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

352352
return $v;
353353
}
@@ -384,7 +384,7 @@ public function g()
384384
}
385385
};
386386

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

@@ -952,7 +952,7 @@ public function doFoo($a)
952952

953953
/** @var T $b */
954954
$b = doFoo();
955-
assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doFoo(), argument)', $b);
955+
assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doFoo(), parameter)', $b);
956956
}
957957

958958
/**
@@ -965,7 +965,7 @@ public function doBar($a)
965965

966966
/** @var T $b */
967967
$b = doFoo();
968-
assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doBar(), argument)', $b);
968+
assertType('T (method PHPStan\Generics\FunctionsAssertType\ClassWithMethodCachingIssue::doBar(), parameter)', $b);
969969
}
970970

971971
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace PropertyTemplateTag;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class BaseObject { }
8+
9+
class ObjectDatabase
10+
{
11+
12+
/** Array of class, then key, then value to object array
13+
* @template T of BaseObject
14+
* @var array<class-string<T>, array<string, array<string, array<string, T>>>> */
15+
private array $objectsByKey = array();
16+
17+
public function LoadObjectsByKey() : void
18+
{
19+
assertType('array<class-string<PropertyTemplateTag\T>, array<string, array<string, array<string, PropertyTemplateTag\T>>>>', $this->objectsByKey);
20+
}
21+
}

0 commit comments

Comments
 (0)