Skip to content

Commit b932769

Browse files
committed
Support @var above class constants
1 parent f7250db commit b932769

21 files changed

+365
-14
lines changed

src/Analyser/MutatingScope.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use PhpParser\NodeFinder;
2929
use PHPStan\Node\ExecutionEndNode;
3030
use PHPStan\Parser\Parser;
31+
use PHPStan\Reflection\ClassConstantReflection;
3132
use PHPStan\Reflection\ClassMemberReflection;
3233
use PHPStan\Reflection\ClassReflection;
3334
use PHPStan\Reflection\ConstantReflection;
@@ -1890,7 +1891,8 @@ private function resolveType(Expr $node): Type
18901891
if (strtolower($constantName) === 'class') {
18911892
return new GenericClassStringType(new StaticType($this->getClassReflection()));
18921893
}
1893-
return new MixedType();
1894+
1895+
$namesToResolve[] = 'static';
18941896
}
18951897
}
18961898
if (in_array(strtolower($constantClass), $namesToResolve, true)) {
@@ -1926,15 +1928,26 @@ private function resolveType(Expr $node): Type
19261928
continue;
19271929
}
19281930

1929-
$propertyClassReflection = $this->reflectionProvider->getClass($referencedClass);
1930-
if (!$propertyClassReflection->hasConstant($constantName)) {
1931+
$constantClassReflection = $this->reflectionProvider->getClass($referencedClass);
1932+
if (!$constantClassReflection->hasConstant($constantName)) {
19311933
continue;
19321934
}
19331935

1934-
$constantType = $propertyClassReflection->getConstant($constantName)->getValueType();
1936+
$constantReflection = $constantClassReflection->getConstant($constantName);
1937+
if (
1938+
$constantReflection instanceof ClassConstantReflection
1939+
&& $node->class instanceof Name
1940+
&& strtolower((string) $node->class) === 'static'
1941+
&& !$constantClassReflection->isFinal()
1942+
&& !$constantReflection->hasPhpDocType()
1943+
) {
1944+
return new MixedType();
1945+
}
1946+
1947+
$constantType = $constantReflection->getValueType();
19351948
if (
19361949
$constantType instanceof ConstantType
1937-
&& in_array(sprintf('%s::%s', $propertyClassReflection->getName(), $constantName), $this->dynamicConstantNames, true)
1950+
&& in_array(sprintf('%s::%s', $constantClassReflection->getName(), $constantName), $this->dynamicConstantNames, true)
19381951
) {
19391952
$constantType = $constantType->generalize(GeneralizePrecision::lessSpecific());
19401953
}

src/Analyser/NodeScopeResolver.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
use PHPStan\Php\PhpVersion;
8484
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
8585
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
86+
use PHPStan\PhpDoc\StubPhpDocProvider;
8687
use PHPStan\Reflection\ClassReflection;
8788
use PHPStan\Reflection\FunctionReflection;
8889
use PHPStan\Reflection\MethodReflection;
@@ -138,6 +139,8 @@ class NodeScopeResolver
138139

139140
private \PHPStan\Type\FileTypeMapper $fileTypeMapper;
140141

142+
private StubPhpDocProvider $stubPhpDocProvider;
143+
141144
private PhpVersion $phpVersion;
142145

143146
private \PHPStan\PhpDoc\PhpDocInheritanceResolver $phpDocInheritanceResolver;
@@ -189,6 +192,7 @@ public function __construct(
189192
ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider,
190193
Parser $parser,
191194
FileTypeMapper $fileTypeMapper,
195+
StubPhpDocProvider $stubPhpDocProvider,
192196
PhpVersion $phpVersion,
193197
PhpDocInheritanceResolver $phpDocInheritanceResolver,
194198
FileHelper $fileHelper,
@@ -208,6 +212,7 @@ public function __construct(
208212
$this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider;
209213
$this->parser = $parser;
210214
$this->fileTypeMapper = $fileTypeMapper;
215+
$this->stubPhpDocProvider = $stubPhpDocProvider;
211216
$this->phpVersion = $phpVersion;
212217
$this->phpDocInheritanceResolver = $phpDocInheritanceResolver;
213218
$this->fileHelper = $fileHelper;
@@ -1490,6 +1495,8 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, Scope $scop
14901495
return new ClassReflection(
14911496
$this->reflectionProvider,
14921497
$this->fileTypeMapper,
1498+
$this->stubPhpDocProvider,
1499+
$this->phpDocInheritanceResolver,
14931500
$this->phpVersion,
14941501
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
14951502
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),

src/PhpDoc/PhpDocBlock.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,43 @@ public static function resolvePhpDocBlockForProperty(
144144
);
145145
}
146146

147+
/**
148+
* @param string|null $docComment
149+
* @param \PHPStan\Reflection\ClassReflection $classReflection
150+
* @param string|null $trait
151+
* @param string $constantName
152+
* @param string $file
153+
* @param bool|null $explicit
154+
* @param array<int, string> $originalPositionalParameterNames
155+
* @param array<int, string> $newPositionalParameterNames
156+
* @return self
157+
*/
158+
public static function resolvePhpDocBlockForConstant(
159+
?string $docComment,
160+
ClassReflection $classReflection,
161+
?string $trait, // unused
162+
string $constantName,
163+
string $file,
164+
?bool $explicit,
165+
array $originalPositionalParameterNames, // unused
166+
array $newPositionalParameterNames // unused
167+
): self
168+
{
169+
return self::resolvePhpDocBlockTree(
170+
$docComment,
171+
$classReflection,
172+
null,
173+
$constantName,
174+
$file,
175+
'hasConstant',
176+
'getConstant',
177+
__FUNCTION__,
178+
$explicit,
179+
[],
180+
[]
181+
);
182+
}
183+
147184
/**
148185
* @param string|null $docComment
149186
* @param \PHPStan\Reflection\ClassReflection $classReflection
@@ -336,7 +373,7 @@ private static function resolvePhpDocBlockFromClass(
336373
): ?self
337374
{
338375
if ($classReflection->getFileNameWithPhpDocs() !== null && $classReflection->$hasMethodName($name)) {
339-
/** @var \PHPStan\Reflection\PropertyReflection|\PHPStan\Reflection\MethodReflection $parentReflection */
376+
/** @var \PHPStan\Reflection\PropertyReflection|\PHPStan\Reflection\MethodReflection|\PHPStan\Reflection\ConstantReflection $parentReflection */
340377
$parentReflection = $classReflection->$getMethodName($name);
341378
if ($parentReflection->isPrivate()) {
342379
return null;

src/PhpDoc/PhpDocInheritanceResolver.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ public function resolvePhpDocForProperty(
3939
return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $declaringTraitName, null);
4040
}
4141

42+
public function resolvePhpDocForConstant(
43+
?string $docComment,
44+
ClassReflection $classReflection,
45+
string $classReflectionFileName,
46+
string $constantName
47+
): ResolvedPhpDocBlock
48+
{
49+
$phpDocBlock = PhpDocBlock::resolvePhpDocBlockForConstant(
50+
$docComment,
51+
$classReflection,
52+
null,
53+
$constantName,
54+
$classReflectionFileName,
55+
null,
56+
[],
57+
[]
58+
);
59+
60+
return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, null, null);
61+
}
62+
4263
/**
4364
* @param string|null $docComment
4465
* @param string $fileName

src/PhpDoc/StubPhpDocProvider.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class StubPhpDocProvider
2727
/** @var array<string, array<string, ResolvedPhpDocBlock|null>> */
2828
private array $propertyMap = [];
2929

30+
/** @var array<string, array<string, ResolvedPhpDocBlock|null>> */
31+
private array $constantMap = [];
32+
3033
/** @var array<string, array<string, null>> */
3134
private array $methodMap = [];
3235

@@ -46,6 +49,9 @@ class StubPhpDocProvider
4649
/** @var array<string, array<string, array{string, string}>> */
4750
private array $knownPropertiesDocComments = [];
4851

52+
/** @var array<string, array<string, array{string, string}>> */
53+
private array $knownConstantsDocComments = [];
54+
4955
/** @var array<string, array<string, array{string, string}>> */
5056
private array $knownMethodsDocComments = [];
5157

@@ -122,6 +128,32 @@ public function findPropertyPhpDoc(string $className, string $propertyName): ?Re
122128
return null;
123129
}
124130

131+
public function findClassConstantPhpDoc(string $className, string $constantName): ?ResolvedPhpDocBlock
132+
{
133+
if (!$this->isKnownClass($className)) {
134+
return null;
135+
}
136+
137+
if (array_key_exists($constantName, $this->constantMap[$className])) {
138+
return $this->constantMap[$className][$constantName];
139+
}
140+
141+
if (array_key_exists($constantName, $this->knownConstantsDocComments[$className])) {
142+
[$file, $docComment] = $this->knownConstantsDocComments[$className][$constantName];
143+
$this->constantMap[$className][$constantName] = $this->fileTypeMapper->getResolvedPhpDoc(
144+
$file,
145+
$className,
146+
null,
147+
null,
148+
$docComment
149+
);
150+
151+
return $this->constantMap[$className][$constantName];
152+
}
153+
154+
return null;
155+
}
156+
125157
/**
126158
* @param string $className
127159
* @param string $methodName
@@ -306,7 +338,9 @@ private function initializeKnownElementNode(string $stubFile, Node $node): void
306338

307339
$this->methodMap[$className] = [];
308340
$this->propertyMap[$className] = [];
341+
$this->constantMap[$className] = [];
309342
$this->knownPropertiesDocComments[$className] = [];
343+
$this->knownConstantsDocComments[$className] = [];
310344
$this->knownMethodsDocComments[$className] = [];
311345

312346
foreach ($node->stmts as $stmt) {
@@ -319,6 +353,14 @@ private function initializeKnownElementNode(string $stubFile, Node $node): void
319353
}
320354
$this->knownPropertiesDocComments[$className][$property->name->toString()] = [$stubFile, $docComment->getText()];
321355
}
356+
} elseif ($stmt instanceof Node\Stmt\ClassConst) {
357+
foreach ($stmt->consts as $const) {
358+
if ($docComment === null) {
359+
$this->constantMap[$className][$const->name->toString()] = null;
360+
continue;
361+
}
362+
$this->knownConstantsDocComments[$className][$const->name->toString()] = [$stubFile, $docComment->getText()];
363+
}
322364
} elseif ($stmt instanceof Node\Stmt\ClassMethod) {
323365
if ($docComment === null) {
324366
$this->methodMap[$className][$stmt->name->toString()] = null;

src/Reflection/BetterReflection/BetterReflectionProvider.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use PHPStan\File\FileHelper;
2222
use PHPStan\File\RelativePathHelper;
2323
use PHPStan\Php\PhpVersion;
24+
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
2425
use PHPStan\PhpDoc\StubPhpDocProvider;
2526
use PHPStan\PhpDoc\Tag\ParamTag;
2627
use PHPStan\Reflection\ClassNameHelper;
@@ -52,6 +53,8 @@ class BetterReflectionProvider implements ReflectionProvider
5253

5354
private \PHPStan\Type\FileTypeMapper $fileTypeMapper;
5455

56+
private PhpDocInheritanceResolver $phpDocInheritanceResolver;
57+
5558
private PhpVersion $phpVersion;
5659

5760
private \PHPStan\Reflection\SignatureMap\NativeFunctionReflectionProvider $nativeFunctionReflectionProvider;
@@ -84,6 +87,7 @@ public function __construct(
8487
ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider,
8588
ClassReflector $classReflector,
8689
FileTypeMapper $fileTypeMapper,
90+
PhpDocInheritanceResolver $phpDocInheritanceResolver,
8791
PhpVersion $phpVersion,
8892
NativeFunctionReflectionProvider $nativeFunctionReflectionProvider,
8993
StubPhpDocProvider $stubPhpDocProvider,
@@ -101,6 +105,7 @@ public function __construct(
101105
$this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider;
102106
$this->classReflector = $classReflector;
103107
$this->fileTypeMapper = $fileTypeMapper;
108+
$this->phpDocInheritanceResolver = $phpDocInheritanceResolver;
104109
$this->phpVersion = $phpVersion;
105110
$this->nativeFunctionReflectionProvider = $nativeFunctionReflectionProvider;
106111
$this->stubPhpDocProvider = $stubPhpDocProvider;
@@ -155,6 +160,8 @@ public function getClass(string $className): ClassReflection
155160
$classReflection = new ClassReflection(
156161
$this->reflectionProviderProvider->getReflectionProvider(),
157162
$this->fileTypeMapper,
163+
$this->stubPhpDocProvider,
164+
$this->phpDocInheritanceResolver,
158165
$this->phpVersion,
159166
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
160167
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
@@ -227,6 +234,8 @@ public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNo
227234
self::$anonymousClasses[$className] = new ClassReflection(
228235
$this->reflectionProviderProvider->getReflectionProvider(),
229236
$this->fileTypeMapper,
237+
$this->stubPhpDocProvider,
238+
$this->phpDocInheritanceResolver,
230239
$this->phpVersion,
231240
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
232241
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),

src/Reflection/ClassConstantReflection.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ class ClassConstantReflection implements ConstantReflection
1313

1414
private \ReflectionClassConstant $reflection;
1515

16+
private ?Type $phpDocType;
17+
1618
private ?string $deprecatedDescription;
1719

1820
private bool $isDeprecated;
@@ -24,13 +26,15 @@ class ClassConstantReflection implements ConstantReflection
2426
public function __construct(
2527
ClassReflection $declaringClass,
2628
\ReflectionClassConstant $reflection,
29+
?Type $phpDocType,
2730
?string $deprecatedDescription,
2831
bool $isDeprecated,
2932
bool $isInternal
3033
)
3134
{
3235
$this->declaringClass = $declaringClass;
3336
$this->reflection = $reflection;
37+
$this->phpDocType = $phpDocType;
3438
$this->deprecatedDescription = $deprecatedDescription;
3539
$this->isDeprecated = $isDeprecated;
3640
$this->isInternal = $isInternal;
@@ -59,11 +63,21 @@ public function getValue()
5963
return $this->reflection->getValue();
6064
}
6165

66+
public function hasPhpDocType(): bool
67+
{
68+
return $this->phpDocType !== null;
69+
}
70+
6271
public function getValueType(): Type
6372
{
6473
if ($this->valueType === null) {
65-
$this->valueType = ConstantTypeHelper::getTypeFromValue($this->getValue());
74+
if ($this->phpDocType === null) {
75+
$this->valueType = ConstantTypeHelper::getTypeFromValue($this->getValue());
76+
} else {
77+
$this->valueType = $this->phpDocType;
78+
}
6679
}
80+
6781
return $this->valueType;
6882
}
6983

0 commit comments

Comments
 (0)