Skip to content

Commit bb31aab

Browse files
schlndhondrejmirtes
authored andcommitted
Fix BackedEnum::tryFrom not being nullable
1 parent 706ba08 commit bb31aab

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,31 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
5454
}
5555

5656
$resultEnumCases = [];
57+
$addNull = false;
58+
foreach ($valueType->getConstantScalarValues() as $value) {
59+
$hasMatching = false;
5760
foreach ($enumCases as $enumCase) {
5861
if ($enumCase->getBackingValueType() === null) {
5962
continue;
6063
}
64+
6165
$enumCaseValues = $enumCase->getBackingValueType()->getConstantScalarValues();
6266
if (count($enumCaseValues) !== 1) {
6367
continue;
6468
}
6569

66-
foreach ($valueType->getConstantScalarValues() as $value) {
6770
if ($value === $enumCaseValues[0]) {
6871
$resultEnumCases[] = new EnumCaseObjectType($enumCase->getDeclaringEnum()->getName(), $enumCase->getName(), $enumCase->getDeclaringEnum());
72+
$hasMatching = true;
6973
break;
7074
}
7175
}
76+
77+
if ($hasMatching) {
78+
continue;
79+
}
80+
81+
$addNull = true;
7282
}
7383

7484
if (count($resultEnumCases) === 0) {
@@ -79,7 +89,12 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
7989
return null;
8090
}
8191

82-
return TypeCombinator::union(...$resultEnumCases);
92+
$result = TypeCombinator::union(...$resultEnumCases);
93+
if ($addNull && $methodReflection->getName() === 'tryFrom') {
94+
return TypeCombinator::addNull($result);
95+
}
96+
97+
return $result;
8398
}
8499

85100
}

tests/PHPStan/Analyser/data/enum-from.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?php // lint >= 8.1
1+
<?php declare(strict_types=1); // lint >= 8.1
22

33
namespace EnumFrom;
44

@@ -12,6 +12,13 @@ enum FooIntegerEnum: int
1212

1313
}
1414

15+
enum FooIntegerEnumSubset: int
16+
{
17+
18+
case BAR = 1;
19+
20+
}
21+
1522
enum FooStringEnum: string
1623
{
1724

@@ -20,6 +27,13 @@ enum FooStringEnum: string
2027

2128
}
2229

30+
enum FooNumericStringEnum: string
31+
{
32+
33+
case ONE = '1';
34+
35+
}
36+
2337
class Foo
2438
{
2539

@@ -49,6 +63,24 @@ public function doFoo(): void
4963
assertType('EnumFrom\FooStringEnum::BAZ', FooStringEnum::tryFrom('baz'));
5064
assertType('EnumFrom\FooStringEnum::BAZ', FooStringEnum::tryFrom(FooStringEnum::BAZ->value));
5165
assertType('EnumFrom\FooStringEnum::BAZ', FooStringEnum::from(FooStringEnum::BAZ->value));
66+
67+
assertType('null', FooIntegerEnum::tryFrom('1'));
68+
assertType('null', FooIntegerEnum::tryFrom(1.0));
69+
assertType('null', FooIntegerEnum::tryFrom(1.0001));
70+
assertType('null', FooIntegerEnum::tryFrom(true));
71+
assertType('null', FooNumericStringEnum::tryFrom(1));
72+
}
73+
74+
public function supersetToSubset(FooIntegerEnum $foo): void
75+
{
76+
assertType('EnumFrom\FooIntegerEnumSubset::BAR|null', FooIntegerEnumSubset::tryFrom($foo->value));
77+
assertType('EnumFrom\FooIntegerEnumSubset::BAR', FooIntegerEnumSubset::from($foo->value));
78+
}
79+
80+
public function subsetToSuperset(FooIntegerEnumSubset $foo): void
81+
{
82+
assertType('EnumFrom\FooIntegerEnum::BAR', FooIntegerEnum::tryFrom($foo->value));
83+
assertType('EnumFrom\FooIntegerEnum::BAR', FooIntegerEnum::from($foo->value));
5284
}
5385

5486
}

0 commit comments

Comments
 (0)