Skip to content

Commit cccf92c

Browse files
Fix ClassConstantAccessType to use declared types for non-final classes
When the inner type is still a StaticType (not yet resolved to a concrete class), use the declared type (native or PHPDoc) instead of the literal value for non-final constants on non-final classes. For final constants or final classes, the literal value is preserved since subclasses cannot override them. Added test cases for: - Constants with native type (const string) - Constants with PHPDoc type (@var non-empty-string) - Constants with both native and PHPDoc types - Final constants on non-final classes - Untyped constants Co-authored-by: Ondřej Mirtes <ondrejmirtes@users.noreply.github.com>
1 parent b0abcf3 commit cccf92c

File tree

2 files changed

+124
-3
lines changed

2 files changed

+124
-3
lines changed

src/Type/ClassConstantAccessType.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,25 @@ public function isResolvable(): bool
5151

5252
protected function getResult(): Type
5353
{
54-
if ($this->type->hasConstant($this->constantName)->yes()) {
55-
return $this->type->getConstant($this->constantName)->getValueType();
54+
if (!$this->type->hasConstant($this->constantName)->yes()) {
55+
return new ErrorType();
5656
}
5757

58-
return new ErrorType();
58+
$constantReflection = $this->type->getConstant($this->constantName);
59+
60+
if (
61+
$this->type instanceof StaticType
62+
&& !$this->type->getClassReflection()->isFinal()
63+
&& !$constantReflection->isFinal()
64+
) {
65+
if ($constantReflection->hasNativeType() || $constantReflection->hasPhpDocType()) {
66+
return $constantReflection->getValueType();
67+
}
68+
69+
return new MixedType();
70+
}
71+
72+
return $constantReflection->getValueType();
5973
}
6074

6175
/**

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

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,110 @@ function testFinal(FinalFoo $foo): void
4141
{
4242
assertType("'foo'", $foo->test());
4343
}
44+
45+
class WithNativeType
46+
{
47+
const string FOO_BAR = 'foo';
48+
49+
/** @return static::FOO_BAR */
50+
public function test(): string
51+
{
52+
return static::FOO_BAR;
53+
}
54+
}
55+
56+
class WithNativeTypeChild extends WithNativeType
57+
{
58+
const string FOO_BAR = 'bar';
59+
}
60+
61+
function testNativeType(WithNativeType $foo, WithNativeTypeChild $bar): void
62+
{
63+
assertType('string', $foo->test());
64+
assertType('string', $bar->test());
65+
}
66+
67+
class WithPhpDocType
68+
{
69+
/** @var non-empty-string */
70+
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+
class WithPhpDocTypeChild extends WithPhpDocType
80+
{
81+
/** @var non-empty-string */
82+
const FOO_BAR = 'bar';
83+
}
84+
85+
function testPhpDocType(WithPhpDocType $foo, WithPhpDocTypeChild $bar): void
86+
{
87+
assertType('non-empty-string', $foo->test());
88+
assertType('non-empty-string', $bar->test());
89+
}
90+
91+
class WithBothTypes
92+
{
93+
/** @var non-empty-string */
94+
const string FOO_BAR = 'foo';
95+
96+
/** @return static::FOO_BAR */
97+
public function test(): string
98+
{
99+
return static::FOO_BAR;
100+
}
101+
}
102+
103+
class WithBothTypesChild extends WithBothTypes
104+
{
105+
/** @var non-empty-string */
106+
const string FOO_BAR = 'bar';
107+
}
108+
109+
function testBothTypes(WithBothTypes $foo, WithBothTypesChild $bar): void
110+
{
111+
assertType('non-empty-string', $foo->test());
112+
assertType('non-empty-string', $bar->test());
113+
}
114+
115+
class WithFinalConstant
116+
{
117+
final const FOO_BAR = 'foo';
118+
119+
/** @return static::FOO_BAR */
120+
public function test(): string
121+
{
122+
return static::FOO_BAR;
123+
}
124+
}
125+
126+
class WithFinalConstantChild extends WithFinalConstant
127+
{
128+
}
129+
130+
function testFinalConstant(WithFinalConstant $foo, WithFinalConstantChild $bar): void
131+
{
132+
assertType("'foo'", $foo->test());
133+
assertType("'foo'", $bar->test());
134+
}
135+
136+
class WithUntypedConstant
137+
{
138+
const FOO_BAR = 'foo';
139+
140+
/** @return static::FOO_BAR */
141+
public function test(): string
142+
{
143+
return static::FOO_BAR;
144+
}
145+
}
146+
147+
function testUntypedConstant(WithUntypedConstant $foo): void
148+
{
149+
assertType("'foo'", $foo->test());
150+
}

0 commit comments

Comments
 (0)