Skip to content

Commit 6f0afd8

Browse files
phpstan-botclaude
andcommitted
Simplify extension to use synthetic ClassConstFetch via Scope::getType()
Replace manual constant type resolution (enum case checks, getValueType()) with synthetic ClassConstFetch nodes passed to Scope::getType(). This reuses the existing type resolution in InitializerExprTypeResolver which already handles enum cases, final/non-final constants, typed constants, and circular references. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 32710c4 commit 6f0afd8

1 file changed

Lines changed: 65 additions & 59 deletions

File tree

src/Type/Php/ReflectionClassGetConstantsDynamicReturnTypeExtension.php

Lines changed: 65 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace PHPStan\Type\Php;
44

5+
use PhpParser\Node\Expr\ClassConstFetch;
56
use PhpParser\Node\Expr\MethodCall;
7+
use PhpParser\Node\Identifier;
8+
use PhpParser\Node\Name\FullyQualified;
69
use PHPStan\Analyser\Scope;
710
use PHPStan\DependencyInjection\AutowiredService;
811
use PHPStan\Reflection\ClassReflection;
@@ -11,7 +14,6 @@
1114
use PHPStan\Type\Constant\ConstantBooleanType;
1215
use PHPStan\Type\Constant\ConstantStringType;
1316
use PHPStan\Type\DynamicMethodReturnTypeExtension;
14-
use PHPStan\Type\Enum\EnumCaseObjectType;
1517
use PHPStan\Type\ObjectWithoutClassType;
1618
use PHPStan\Type\Type;
1719
use PHPStan\Type\TypeCombinator;
@@ -56,7 +58,16 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
5658
? $scope->getType($methodCall->getArgs()[0]->value)
5759
: null;
5860

59-
return $this->resolveGetConstants($classReflections, $filterType);
61+
return $this->resolveGetConstants($scope, $classReflections, $filterType);
62+
}
63+
64+
/** @param non-empty-string $name */
65+
private function getConstantType(Scope $scope, ClassReflection $classReflection, string $name): Type
66+
{
67+
return $scope->getType(new ClassConstFetch(
68+
new FullyQualified($classReflection->getName()),
69+
new Identifier($name),
70+
));
6071
}
6172

6273
/**
@@ -76,10 +87,11 @@ private function resolveGetConstant(MethodCall $methodCall, Scope $scope, array
7687
foreach ($classReflections as $classReflection) {
7788
foreach ($constantNames as $constantName) {
7889
$name = $constantName->getValue();
79-
if ($classReflection->isEnum() && $classReflection->hasEnumCase($name)) {
80-
$types[] = new EnumCaseObjectType($classReflection->getName(), $name);
81-
} elseif ($classReflection->hasConstant($name)) {
82-
$types[] = $classReflection->getConstant($name)->getValueType();
90+
if ($name === '') {
91+
continue;
92+
}
93+
if ($classReflection->hasConstant($name)) {
94+
$types[] = $this->getConstantType($scope, $classReflection, $name);
8395
} else {
8496
$types[] = new ConstantBooleanType(false);
8597
}
@@ -95,8 +107,8 @@ private function resolveGetConstant(MethodCall $methodCall, Scope $scope, array
95107

96108
$allConstantTypes = [];
97109
foreach ($classReflections as $classReflection) {
98-
foreach ($this->getClassConstants($classReflection) as [$name, $valueType]) {
99-
$allConstantTypes[] = $valueType;
110+
foreach ($this->getConstantNames($classReflection) as $name) {
111+
$allConstantTypes[] = $this->getConstantType($scope, $classReflection, $name);
100112
}
101113
}
102114

@@ -112,61 +124,61 @@ private function resolveGetConstant(MethodCall $methodCall, Scope $scope, array
112124
/**
113125
* @param list<ClassReflection> $classReflections
114126
*/
115-
private function resolveGetConstants(array $classReflections, ?Type $filterType): ?Type
127+
private function resolveGetConstants(Scope $scope, array $classReflections, ?Type $filterType): ?Type
116128
{
117-
$filter = null;
118-
$filterIsUncertain = false;
119-
if ($filterType !== null) {
120-
$filterScalars = $filterType->getConstantScalarValues();
121-
$intFilters = [];
122-
foreach ($filterScalars as $scalar) {
123-
if (!is_int($scalar)) {
124-
$intFilters = null;
125-
break;
126-
}
127-
$intFilters[] = $scalar;
128-
}
129+
if ($filterType === null) {
130+
return $this->buildConstantsArray($scope, $classReflections, null, false);
131+
}
129132

130-
if ($intFilters !== null && count($intFilters) === 1) {
131-
$filter = $intFilters[0];
132-
} elseif ($intFilters !== null && count($intFilters) > 1) {
133-
return $this->resolveGetConstantsForMultipleFilters($classReflections, $intFilters);
134-
} else {
135-
$filterIsUncertain = true;
133+
$filterScalars = $filterType->getConstantScalarValues();
134+
$intFilters = [];
135+
foreach ($filterScalars as $scalar) {
136+
if (!is_int($scalar)) {
137+
$intFilters = null;
138+
break;
136139
}
140+
$intFilters[] = $scalar;
137141
}
138142

139-
$types = [];
140-
foreach ($classReflections as $classReflection) {
141-
$builder = ConstantArrayTypeBuilder::createEmpty();
142-
foreach ($this->getClassConstants($classReflection, $filter) as [$name, $valueType]) {
143-
$builder->setOffsetValueType(new ConstantStringType($name), $valueType, $filterIsUncertain);
144-
}
145-
$types[] = $builder->getArray();
143+
if ($intFilters !== null && count($intFilters) === 1) {
144+
return $this->buildConstantsArray($scope, $classReflections, $intFilters[0], false);
146145
}
147146

148-
if (count($types) === 0) {
149-
return null;
147+
if ($intFilters !== null && count($intFilters) > 1) {
148+
$types = [];
149+
foreach ($intFilters as $filter) {
150+
$result = $this->buildConstantsArray($scope, $classReflections, $filter, false);
151+
if ($result !== null) {
152+
$types[] = $result;
153+
}
154+
}
155+
156+
if (count($types) === 0) {
157+
return null;
158+
}
159+
160+
return TypeCombinator::union(...$types);
150161
}
151162

152-
return TypeCombinator::union(...$types);
163+
return $this->buildConstantsArray($scope, $classReflections, null, true);
153164
}
154165

155166
/**
156167
* @param list<ClassReflection> $classReflections
157-
* @param list<int> $filters
158168
*/
159-
private function resolveGetConstantsForMultipleFilters(array $classReflections, array $filters): ?Type
169+
private function buildConstantsArray(Scope $scope, array $classReflections, ?int $filter, bool $optional): ?Type
160170
{
161171
$types = [];
162-
foreach ($filters as $filter) {
163-
foreach ($classReflections as $classReflection) {
164-
$builder = ConstantArrayTypeBuilder::createEmpty();
165-
foreach ($this->getClassConstants($classReflection, $filter) as [$name, $valueType]) {
166-
$builder->setOffsetValueType(new ConstantStringType($name), $valueType);
167-
}
168-
$types[] = $builder->getArray();
172+
foreach ($classReflections as $classReflection) {
173+
$builder = ConstantArrayTypeBuilder::createEmpty();
174+
foreach ($this->getConstantNames($classReflection, $filter) as $name) {
175+
$builder->setOffsetValueType(
176+
new ConstantStringType($name),
177+
$this->getConstantType($scope, $classReflection, $name),
178+
$optional,
179+
);
169180
}
181+
$types[] = $builder->getArray();
170182
}
171183

172184
if (count($types) === 0) {
@@ -177,31 +189,25 @@ private function resolveGetConstantsForMultipleFilters(array $classReflections,
177189
}
178190

179191
/**
180-
* @return list<array{string, Type}>
192+
* @return list<non-empty-string>
181193
*/
182-
private function getClassConstants(ClassReflection $classReflection, ?int $filter = null): array
194+
private function getConstantNames(ClassReflection $classReflection, ?int $filter = null): array
183195
{
184-
$constants = [];
196+
$names = [];
185197
foreach ($classReflection->getNativeReflection()->getReflectionConstants() as $reflectionConstant) {
186-
$constantName = $reflectionConstant->getName();
187-
188198
if ($filter !== null && ($reflectionConstant->getModifiers() & $filter) === 0) {
189199
continue;
190200
}
191201

192-
if ($classReflection->isEnum() && $classReflection->hasEnumCase($constantName)) {
193-
$constants[] = [$constantName, new EnumCaseObjectType($classReflection->getName(), $constantName)];
194-
continue;
195-
}
196-
197-
if (!$classReflection->hasConstant($constantName)) {
202+
$name = $reflectionConstant->getName();
203+
if ($name === '') {
198204
continue;
199205
}
200206

201-
$constants[] = [$constantName, $classReflection->getConstant($constantName)->getValueType()];
207+
$names[] = $name;
202208
}
203209

204-
return $constants;
210+
return $names;
205211
}
206212

207213
}

0 commit comments

Comments
 (0)