Skip to content

Commit d0c5099

Browse files
Align ClassConstantAccessType::getResult() with InitializerExprTypeResolver logic
Restructured getResult() to match the $isObject path in InitializerExprTypeResolver::getClassConstFetchTypeByReflection(): - Extract ClassReflection first via getObjectClassReflections() - Add enum case handling (EnumCaseObjectType) - Match exact condition structure: class not final AND constant not final AND no phpDocType AND no nativeType → mixed - Otherwise use getValueType() (declared type for typed constants, literal for untyped constants) Added test cases for final child class inheriting @return static::CONST and for final typed constant on non-final class. Co-authored-by: Ondřej Mirtes <ondrejmirtes@users.noreply.github.com>
1 parent e450d3c commit d0c5099

File tree

2 files changed

+49
-10
lines changed

2 files changed

+49
-10
lines changed

src/Type/ClassConstantAccessType.php

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,33 @@ public function isResolvable(): bool
5151

5252
protected function getResult(): Type
5353
{
54-
if (!$this->type->hasConstant($this->constantName)->yes()) {
54+
$classReflections = $this->type->getObjectClassReflections();
55+
if (count($classReflections) !== 1) {
56+
if (!$this->type->hasConstant($this->constantName)->yes()) {
57+
return new ErrorType();
58+
}
59+
60+
return $this->type->getConstant($this->constantName)->getValueType();
61+
}
62+
63+
$constantClassReflection = $classReflections[0];
64+
if (!$constantClassReflection->hasConstant($this->constantName)) {
5565
return new ErrorType();
5666
}
5767

58-
$constantReflection = $this->type->getConstant($this->constantName);
68+
if ($constantClassReflection->isEnum() && $constantClassReflection->hasEnumCase($this->constantName)) {
69+
return new Enum\EnumCaseObjectType($constantClassReflection->getName(), $this->constantName);
70+
}
5971

60-
if (!$constantReflection->isFinal()) {
61-
$classReflections = $this->type->getObjectClassReflections();
62-
if (count($classReflections) === 1 && !$classReflections[0]->isFinal()) {
63-
if ($constantReflection->hasNativeType() || $constantReflection->hasPhpDocType()) {
64-
return $constantReflection->getValueType();
65-
}
72+
$constantReflection = $constantClassReflection->getConstant($this->constantName);
6673

67-
return new MixedType();
68-
}
74+
if (
75+
!$constantClassReflection->isFinal()
76+
&& !$constantReflection->isFinal()
77+
&& !$constantReflection->hasPhpDocType()
78+
&& !$constantReflection->hasNativeType()
79+
) {
80+
return new MixedType();
6981
}
7082

7183
return $constantReflection->getValueType();

tests/PHPStan/Analyser/nsrt/bug-13828.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,30 @@ function testUntypedConstant(WithUntypedConstant $foo): void
148148
{
149149
assertType('mixed', $foo->test());
150150
}
151+
152+
final class FinalChild extends FooBar
153+
{
154+
const FOO_BAR = 'baz';
155+
}
156+
157+
function testFinalChild(FinalChild $foo): void
158+
{
159+
assertType("'baz'", $foo->test());
160+
}
161+
162+
class WithFinalTypedConstant
163+
{
164+
/** @var non-empty-string */
165+
final const string FOO_BAR = 'foo';
166+
167+
/** @return static::FOO_BAR */
168+
public function test(): string
169+
{
170+
return static::FOO_BAR;
171+
}
172+
}
173+
174+
function testFinalTypedConstant(WithFinalTypedConstant $foo): void
175+
{
176+
assertType('non-empty-string', $foo->test());
177+
}

0 commit comments

Comments
 (0)