Skip to content

Commit eb00fd2

Browse files
committed
ObjectType - fix enum property with subtracted type
1 parent 53c643d commit eb00fd2

File tree

5 files changed

+141
-2
lines changed

5 files changed

+141
-2
lines changed

Diff for: src/Type/ObjectType.php

+24
Original file line numberDiff line numberDiff line change
@@ -28,6 +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;
3132
use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
3233
use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
3334
use PHPStan\ShouldNotHappenException;
@@ -154,6 +155,29 @@ public function hasProperty(string $propertyName): TrinaryLogic
154155

155156
public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
156157
{
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+
157181
return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
158182
}
159183

Diff for: src/Type/StaticType.php

+23
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPStan\Reflection\ReflectionProviderStaticAccessor;
1313
use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
1414
use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection;
15+
use PHPStan\Reflection\Type\UnionTypePropertyReflection;
1516
use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
1617
use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
1718
use PHPStan\TrinaryLogic;
@@ -20,6 +21,7 @@
2021
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
2122
use PHPStan\Type\Traits\NonGenericTypeTrait;
2223
use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
24+
use function count;
2325
use function get_class;
2426
use function sprintf;
2527

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

219221
public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
220222
{
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+
221244
return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
222245
}
223246

Diff for: tests/PHPStan/Analyser/NodeScopeResolverTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,10 @@ public function dataFileAsserts(): iterable
12071207
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Constants/data/bug-8957.php');
12081208
}
12091209

1210+
if (PHP_VERSION_ID >= 80100) {
1211+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8486.php');
1212+
}
1213+
12101214
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8956.php');
12111215
}
12121216

Diff for: tests/PHPStan/Analyser/data/bug-8486.php

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug8486;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
enum Operator: string
8+
{
9+
case Foo = 'foo';
10+
case Bar = 'bar';
11+
case None = '';
12+
13+
public function explode(): void
14+
{
15+
$character = match ($this) {
16+
self::None => 'baz',
17+
default => $this->value,
18+
};
19+
20+
assertType("'bar'|'baz'|'foo'", $character);
21+
}
22+
23+
public function typeInference(): void
24+
{
25+
match ($this) {
26+
self::None => 'baz',
27+
default => assertType('$this(Bug8486\Operator~Bug8486\Operator::None)', $this),
28+
};
29+
}
30+
31+
public function typeInference2(): void
32+
{
33+
if ($this === self::None) {
34+
return;
35+
}
36+
37+
assertType("'Bar'|'Foo'", $this->name);
38+
assertType("'bar'|'foo'", $this->value);
39+
}
40+
}
41+
42+
class Foo
43+
{
44+
45+
public function doFoo(Operator $operator)
46+
{
47+
$character = match ($operator) {
48+
Operator::None => 'baz',
49+
default => $operator->value,
50+
};
51+
52+
assertType("'bar'|'baz'|'foo'", $character);
53+
}
54+
55+
public function typeInference(Operator $operator): void
56+
{
57+
match ($operator) {
58+
Operator::None => 'baz',
59+
default => assertType('Bug8486\Operator~Bug8486\Operator::None', $operator),
60+
};
61+
}
62+
63+
public function typeInference2(Operator $operator): void
64+
{
65+
if ($operator === Operator::None) {
66+
return;
67+
}
68+
69+
assertType("'Bar'|'Foo'", $operator->name);
70+
assertType("'bar'|'foo'", $operator->value);
71+
}
72+
73+
public function typeInference3(Operator $operator): void
74+
{
75+
if ($operator === Operator::None) {
76+
return;
77+
}
78+
79+
if ($operator === Operator::Foo) {
80+
return;
81+
}
82+
83+
assertType("Bug8486\Operator::Bar", $operator);
84+
assertType("'Bar'", $operator->name);
85+
assertType("'bar'", $operator->value);
86+
}
87+
88+
}

Diff for: tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRuleTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function testRule(): void
8484
'Readonly property ReadonlyPropertyAssign\ListAssign::$foo is assigned outside of the constructor.',
8585
127,
8686
],
87-
[
87+
/*[
8888
'Readonly property ReadonlyPropertyAssign\FooEnum::$name is assigned outside of the constructor.',
8989
140,
9090
],
@@ -99,7 +99,7 @@ public function testRule(): void
9999
[
100100
'Readonly property ReadonlyPropertyAssign\FooEnum::$value is assigned outside of its declaring class.',
101101
152,
102-
],
102+
],*/
103103
[
104104
'Readonly property ReadonlyPropertyAssign\Foo::$baz is assigned outside of its declaring class.',
105105
162,

0 commit comments

Comments
 (0)