Skip to content

Commit 0292be8

Browse files
phpstan-botclaude
andcommitted
Return exact value for final constants in ClassConstantAccessType::getResult()
Add getInitializerExprType() to ClassConstantReflection to retrieve the concrete initializer value type, bypassing PHPDoc/native type declarations. Use it in ClassConstantAccessType::getResult() for final constants, since they cannot be overridden and their exact value is always known. Update test expectations for final typed constants to expect exact values instead of declared types. Add test cases for non-final class with non-final typed constants to verify they correctly resolve to PHPDoc/native types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a910894 commit 0292be8

7 files changed

Lines changed: 87 additions & 4 deletions

File tree

src/Reflection/ClassConstantReflection.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@ public function getNativeType(): ?Type;
4040

4141
public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock;
4242

43+
public function getInitializerExprType(): Type;
44+
4345
}

src/Reflection/Dummy/DummyClassConstantReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,9 @@ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
122122
return null;
123123
}
124124

125+
public function getInitializerExprType(): Type
126+
{
127+
return new MixedType();
128+
}
129+
125130
}

src/Reflection/RealClassClassConstantReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ public function getValueType(): Type
9191
return $this->valueType;
9292
}
9393

94+
public function getInitializerExprType(): Type
95+
{
96+
return $this->initializerExprTypeResolver->getType($this->getValueExpr(), InitializerExprContext::fromClassReflection($this->declaringClass));
97+
}
98+
9499
public function getDeclaringClass(): ClassReflection
95100
{
96101
return $this->declaringClass;

src/Rules/RestrictedUsage/RewrittenDeclaringClassClassConstantReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,9 @@ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
119119
return $this->constantReflection->getResolvedPhpDoc();
120120
}
121121

122+
public function getInitializerExprType(): Type
123+
{
124+
return $this->constantReflection->getInitializerExprType();
125+
}
126+
122127
}

src/Type/ClassConstantAccessType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ protected function getResult(): Type
8888
$isFinalClass = count($classReflections) === 1 && $classReflections[0]->isFinal();
8989

9090
if ($isFinalClass || $constantReflection->isFinal()) {
91-
return $constantReflection->getValueType();
91+
return $constantReflection->getInitializerExprType();
9292
}
9393

9494
if (!$constantReflection->hasPhpDocType() && !$constantReflection->hasNativeType()) {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ public function test(): string
173173

174174
function testFinalTypedConstant(WithFinalTypedConstant $foo): void
175175
{
176-
assertType('non-empty-string', $foo->test());
176+
assertType("'foo'", $foo->test());
177177
}
178178

179179
final class FinalClassWithNativeType
@@ -240,7 +240,7 @@ public function test(): string
240240

241241
function testFinalPhpDocConstant(WithFinalPhpDocConstant $foo): void
242242
{
243-
assertType('non-empty-string', $foo->test());
243+
assertType("'foo'", $foo->test());
244244
}
245245

246246
class WithFinalNativeConstant
@@ -256,5 +256,5 @@ public function test(): string
256256

257257
function testFinalNativeConstant(WithFinalNativeConstant $foo): void
258258
{
259-
assertType('string', $foo->test());
259+
assertType("'foo'", $foo->test());
260260
}

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,69 @@ function test(FooBar $foo, BarBaz $bar, FinalBarBaz $baz): void
3131
assertType('mixed', $bar->test());
3232
assertType("'bar'", $baz->test());
3333
}
34+
35+
class WithNativeType
36+
{
37+
const string FOO_BAR = 'foo';
38+
39+
/** @return static::FOO_BAR */
40+
public function test(): string
41+
{
42+
return static::FOO_BAR;
43+
}
44+
}
45+
46+
function testNativeType(WithNativeType $foo): void
47+
{
48+
assertType('string', $foo->test());
49+
}
50+
51+
class WithPhpDocType
52+
{
53+
/** @var non-empty-string */
54+
const FOO_BAR = 'foo';
55+
56+
/** @return static::FOO_BAR */
57+
public function test(): string
58+
{
59+
return static::FOO_BAR;
60+
}
61+
}
62+
63+
function testPhpDocType(WithPhpDocType $foo): void
64+
{
65+
assertType('non-empty-string', $foo->test());
66+
}
67+
68+
class WithFinalConstant
69+
{
70+
final const FOO_BAR = 'foo';
71+
72+
/** @return static::FOO_BAR */
73+
public function test(): string
74+
{
75+
return static::FOO_BAR;
76+
}
77+
}
78+
79+
function testFinalConstant(WithFinalConstant $foo): void
80+
{
81+
assertType("'foo'", $foo->test());
82+
}
83+
84+
class WithFinalTypedConstant
85+
{
86+
/** @var non-empty-string */
87+
final const string FOO_BAR = 'foo';
88+
89+
/** @return static::FOO_BAR */
90+
public function test(): string
91+
{
92+
return static::FOO_BAR;
93+
}
94+
}
95+
96+
function testFinalTypedConstant(WithFinalTypedConstant $foo): void
97+
{
98+
assertType("'foo'", $foo->test());
99+
}

0 commit comments

Comments
 (0)