Skip to content

Commit 32710c4

Browse files
phpstan-botclaude
andcommitted
Compute precise return types for getConstants() with multiple constant filter values
Instead of making all array keys optional when the filter has multiple constant scalar values, compute the result for each filter value separately and return their union. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d7dfaa6 commit 32710c4

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

src/Type/Php/ReflectionClassGetConstantsDynamicReturnTypeExtension.php

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,19 @@ private function resolveGetConstants(array $classReflections, ?Type $filterType)
118118
$filterIsUncertain = false;
119119
if ($filterType !== null) {
120120
$filterScalars = $filterType->getConstantScalarValues();
121-
if (count($filterScalars) === 1 && is_int($filterScalars[0])) {
122-
$filter = $filterScalars[0];
121+
$intFilters = [];
122+
foreach ($filterScalars as $scalar) {
123+
if (!is_int($scalar)) {
124+
$intFilters = null;
125+
break;
126+
}
127+
$intFilters[] = $scalar;
128+
}
129+
130+
if ($intFilters !== null && count($intFilters) === 1) {
131+
$filter = $intFilters[0];
132+
} elseif ($intFilters !== null && count($intFilters) > 1) {
133+
return $this->resolveGetConstantsForMultipleFilters($classReflections, $intFilters);
123134
} else {
124135
$filterIsUncertain = true;
125136
}
@@ -141,6 +152,30 @@ private function resolveGetConstants(array $classReflections, ?Type $filterType)
141152
return TypeCombinator::union(...$types);
142153
}
143154

155+
/**
156+
* @param list<ClassReflection> $classReflections
157+
* @param list<int> $filters
158+
*/
159+
private function resolveGetConstantsForMultipleFilters(array $classReflections, array $filters): ?Type
160+
{
161+
$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();
169+
}
170+
}
171+
172+
if (count($types) === 0) {
173+
return null;
174+
}
175+
176+
return TypeCombinator::union(...$types);
177+
}
178+
144179
/**
145180
* @return list<array{string, Type}>
146181
*/

tests/PHPStan/Analyser/nsrt/reflection-class-get-constants.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,12 @@ function testGetConstantsWithDynamicFilter(ReflectionClass $ref, int $filter): v
194194
{
195195
assertType("array{A?: 1, B?: 'hello', C?: 3.14, D?: true}", $ref->getConstants($filter));
196196
}
197+
198+
/**
199+
* @param ReflectionClass<Foo> $ref
200+
* @param \ReflectionClassConstant::IS_PUBLIC|\ReflectionClassConstant::IS_PROTECTED $filter
201+
*/
202+
function testGetConstantsWithMultipleConstantFilters(ReflectionClass $ref, int $filter): void
203+
{
204+
assertType("array{A: 1, B: 'hello'}|array{C: 3.14}", $ref->getConstants($filter));
205+
}

0 commit comments

Comments
 (0)