Skip to content

Commit 05b85ba

Browse files
committed
Fix ->value on unions of enums
1 parent 9a598b2 commit 05b85ba

7 files changed

+101
-57
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Reflection\Php;
4+
5+
use PHPStan\Reflection\PropertyReflection;
6+
use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
7+
use PHPStan\Type\Type;
8+
9+
class EnumUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection
10+
{
11+
12+
public function __construct(private EnumPropertyReflection $property)
13+
{
14+
}
15+
16+
public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection
17+
{
18+
return $this;
19+
}
20+
21+
public function getNakedProperty(): PropertyReflection
22+
{
23+
return $this->property;
24+
}
25+
26+
public function getTransformedProperty(): PropertyReflection
27+
{
28+
return $this->property;
29+
}
30+
31+
public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection
32+
{
33+
return $this;
34+
}
35+
36+
}

src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php

+1-4
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
class UnionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection
1010
{
1111

12-
private string $propertyName;
13-
1412
private ?PropertyReflection $transformedProperty = null;
1513

1614
private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null;
@@ -19,11 +17,10 @@ class UnionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedProper
1917
* @param UnresolvedPropertyPrototypeReflection[] $propertyPrototypes
2018
*/
2119
public function __construct(
22-
string $propertyName,
20+
private string $propertyName,
2321
private array $propertyPrototypes,
2422
)
2523
{
26-
$this->propertyName = $propertyName;
2724
}
2825

2926
public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection

src/Type/Enum/EnumCaseObjectType.php

+11-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
use PHPStan\Reflection\ClassMemberAccessAnswerer;
66
use PHPStan\Reflection\ClassReflection;
77
use PHPStan\Reflection\Php\EnumPropertyReflection;
8-
use PHPStan\Reflection\PropertyReflection;
8+
use PHPStan\Reflection\Php\EnumUnresolvedPropertyPrototypeReflection;
9+
use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
910
use PHPStan\ShouldNotHappenException;
1011
use PHPStan\TrinaryLogic;
1112
use PHPStan\Type\AcceptsResult;
@@ -110,15 +111,17 @@ public function tryRemove(Type $typeToRemove): ?Type
110111
return null;
111112
}
112113

113-
public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
114+
public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
114115
{
115116
$classReflection = $this->getClassReflection();
116117
if ($classReflection === null) {
117-
return parent::getProperty($propertyName, $scope);
118+
return parent::getUnresolvedPropertyPrototype($propertyName, $scope);
118119

119120
}
120121
if ($propertyName === 'name') {
121-
return new EnumPropertyReflection($classReflection, new ConstantStringType($this->enumCaseName));
122+
return new EnumUnresolvedPropertyPrototypeReflection(
123+
new EnumPropertyReflection($classReflection, new ConstantStringType($this->enumCaseName)),
124+
);
122125
}
123126

124127
if ($classReflection->isBackedEnum() && $propertyName === 'value') {
@@ -129,11 +132,13 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
129132
throw new ShouldNotHappenException();
130133
}
131134

132-
return new EnumPropertyReflection($classReflection, $valueType);
135+
return new EnumUnresolvedPropertyPrototypeReflection(
136+
new EnumPropertyReflection($classReflection, $valueType),
137+
);
133138
}
134139
}
135140

136-
return parent::getProperty($propertyName, $scope);
141+
return parent::getUnresolvedPropertyPrototype($propertyName, $scope);
137142
}
138143

139144
public function getBackingValueType(): ?Type

src/Type/ObjectType.php

+21-24
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
use PHPStan\Reflection\TrivialParametersAcceptor;
2929
use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection;
3030
use PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection;
31-
use PHPStan\Reflection\Type\UnionTypePropertyReflection;
31+
use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection;
3232
use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
3333
use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
3434
use PHPStan\ShouldNotHappenException;
@@ -155,29 +155,6 @@ public function hasProperty(string $propertyName): TrinaryLogic
155155

156156
public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
157157
{
158-
$classReflection = $this->getClassReflection();
159-
if ($classReflection !== null) {
160-
if ($classReflection->isEnum()) {
161-
if (
162-
$propertyName === 'name'
163-
|| ($propertyName === 'value' && $classReflection->isBackedEnum())
164-
) {
165-
$properties = [];
166-
foreach ($this->getEnumCases() as $enumCase) {
167-
$properties[] = $enumCase->getProperty($propertyName, $scope);
168-
}
169-
170-
if (count($properties) > 0) {
171-
if (count($properties) === 1) {
172-
return $properties[0];
173-
}
174-
175-
return new UnionTypePropertyReflection($properties);
176-
}
177-
}
178-
}
179-
}
180-
181158
return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
182159
}
183160

@@ -199,6 +176,26 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember
199176
throw new ClassNotFoundException($this->className);
200177
}
201178

179+
if ($nakedClassReflection->isEnum()) {
180+
if (
181+
$propertyName === 'name'
182+
|| ($propertyName === 'value' && $nakedClassReflection->isBackedEnum())
183+
) {
184+
$properties = [];
185+
foreach ($this->getEnumCases() as $enumCase) {
186+
$properties[] = $enumCase->getUnresolvedPropertyPrototype($propertyName, $scope);
187+
}
188+
189+
if (count($properties) > 0) {
190+
if (count($properties) === 1) {
191+
return $properties[0];
192+
}
193+
194+
return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $properties);
195+
}
196+
}
197+
}
198+
202199
if (!$nakedClassReflection->hasNativeProperty($propertyName)) {
203200
$nakedClassReflection = $this->getClassReflection();
204201
}

src/Type/StaticType.php

-23
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use PHPStan\Reflection\ReflectionProviderStaticAccessor;
1313
use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
1414
use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection;
15-
use PHPStan\Reflection\Type\UnionTypePropertyReflection;
1615
use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
1716
use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
1817
use PHPStan\TrinaryLogic;
@@ -21,7 +20,6 @@
2120
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
2221
use PHPStan\Type\Traits\NonGenericTypeTrait;
2322
use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
24-
use function count;
2523
use function get_class;
2624
use function sprintf;
2725

@@ -220,27 +218,6 @@ public function hasProperty(string $propertyName): TrinaryLogic
220218

221219
public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
222220
{
223-
$classReflection = $this->getClassReflection();
224-
if ($classReflection->isEnum()) {
225-
if (
226-
$propertyName === 'name'
227-
|| ($propertyName === 'value' && $classReflection->isBackedEnum())
228-
) {
229-
$properties = [];
230-
foreach ($this->getEnumCases() as $enumCase) {
231-
$properties[] = $enumCase->getProperty($propertyName, $scope);
232-
}
233-
234-
if (count($properties) > 0) {
235-
if (count($properties) === 1) {
236-
return $properties[0];
237-
}
238-
239-
return new UnionTypePropertyReflection($properties);
240-
}
241-
}
242-
}
243-
244221
return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
245222
}
246223

tests/PHPStan/Analyser/NodeScopeResolverTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,7 @@ public function dataFileAsserts(): iterable
12091209

12101210
if (PHP_VERSION_ID >= 80100) {
12111211
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8486.php');
1212+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9000.php');
12121213
}
12131214

12141215
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8956.php');
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug9000;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
enum A:string {
8+
case A = "A";
9+
case B = "B";
10+
case C = "C";
11+
}
12+
13+
const A_ARRAY = [
14+
'A' => A::A,
15+
'B' => A::B,
16+
];
17+
18+
/**
19+
* @param string $key
20+
* @return value-of<A_ARRAY>
21+
*/
22+
function testA(string $key): A
23+
{
24+
return A_ARRAY[$key];
25+
}
26+
27+
function (): void {
28+
$test = testA('A');
29+
assertType('Bug9000\A::A|Bug9000\A::B', $test);
30+
assertType("'A'|'B'", $test->value);
31+
};

0 commit comments

Comments
 (0)