Skip to content

Commit 86aa740

Browse files
Fix ClassConstantAccessType resolution and array shape offset handling
1. Add `use function count` to fix coding standard error 2. In getResult(), return value type directly when inner type is StaticType. This prevents premature resolution to `mixed` during decideType() which would cause the native return type to override the PHPDoc type before transformStaticType() has a chance to replace StaticType with a concrete ObjectType. After transformation, the new instance resolves correctly. 3. Revert resolveArrayShapeOffsetType to treat static:: like self:: because array shape keys need concrete ConstantStringType values, not ClassConstantAccessType which resolves to mixed for untyped constants. Co-authored-by: Ondřej Mirtes <ondrejmirtes@users.noreply.github.com>
1 parent 10fdb06 commit 86aa740

File tree

2 files changed

+15
-23
lines changed

2 files changed

+15
-23
lines changed

src/PhpDoc/TypeNodeResolver.php

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,14 +1202,9 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc
12021202
throw new ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode
12031203
}
12041204

1205-
$isStatic = false;
12061205
if ($nameScope->getClassName() !== null) {
12071206
switch (strtolower($constExpr->className)) {
12081207
case 'static':
1209-
$className = $nameScope->getClassName();
1210-
$isStatic = true;
1211-
break;
1212-
12131208
case 'self':
12141209
$className = $nameScope->getClassName();
12151210
break;
@@ -1238,10 +1233,6 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc
12381233

12391234
$classReflection = $this->getReflectionProvider()->getClass($className);
12401235

1241-
if ($isStatic && $classReflection->isFinal()) {
1242-
$isStatic = false;
1243-
}
1244-
12451236
$constantName = $constExpr->name;
12461237
if (Strings::contains($constantName, '*')) {
12471238
// convert * into .*? and escape everything else so the constants can be matched against the pattern
@@ -1258,16 +1249,6 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc
12581249
continue;
12591250
}
12601251

1261-
if ($isStatic) {
1262-
$constantReflection = $classReflection->getConstant($classConstantName);
1263-
if (!$constantReflection->isFinal() && !$constantReflection->hasPhpDocType() && !$constantReflection->hasNativeType()) {
1264-
$constantTypes[] = new MixedType();
1265-
continue;
1266-
}
1267-
$constantTypes[] = $constantReflection->getValueType();
1268-
continue;
1269-
}
1270-
12711252
$declaringClassName = $reflectionConstant->getDeclaringClass()->getName();
12721253
if (!$this->getReflectionProvider()->hasClass($declaringClassName)) {
12731254
continue;
@@ -1296,10 +1277,6 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc
12961277
return new EnumCaseObjectType($classReflection->getName(), $constantName);
12971278
}
12981279

1299-
if ($isStatic) {
1300-
return new ClassConstantAccessType(new StaticType($classReflection), $constantName);
1301-
}
1302-
13031280
$reflectionConstant = $classReflection->getNativeReflection()->getReflectionConstant($constantName);
13041281
if ($reflectionConstant === false) {
13051282
return new ErrorType();

src/Type/ClassConstantAccessType.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPStan\Type\Generic\TemplateTypeVariance;
99
use PHPStan\Type\Traits\LateResolvableTypeTrait;
1010
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
11+
use function count;
1112

1213
final class ClassConstantAccessType implements CompoundType, LateResolvableType
1314
{
@@ -51,6 +52,20 @@ public function isResolvable(): bool
5152

5253
protected function getResult(): Type
5354
{
55+
// When the inner type is still a StaticType, this ClassConstantAccessType
56+
// has not yet been transformed by CalledOnTypeUnresolvedMethodPrototypeReflection.
57+
// Return the value type directly so that decideType() accepts the PHPDoc type
58+
// as compatible with the native return type. Once transformStaticType() replaces
59+
// the StaticType with a concrete ObjectType, traverse() creates a new instance
60+
// whose getResult() applies the full resolution logic below.
61+
if ($this->type instanceof StaticType) {
62+
if (!$this->type->hasConstant($this->constantName)->yes()) {
63+
return new ErrorType();
64+
}
65+
66+
return $this->type->getConstant($this->constantName)->getValueType();
67+
}
68+
5469
$classReflections = $this->type->getObjectClassReflections();
5570
if (count($classReflections) !== 1) {
5671
if (!$this->type->hasConstant($this->constantName)->yes()) {

0 commit comments

Comments
 (0)