diff --git a/src/Reflection/Php/EnumUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Php/EnumUnresolvedPropertyPrototypeReflection.php new file mode 100644 index 000000000..5d76711ce --- /dev/null +++ b/src/Reflection/Php/EnumUnresolvedPropertyPrototypeReflection.php @@ -0,0 +1,36 @@ +property; + } + + public function getTransformedProperty(): PropertyReflection + { + return $this->property; + } + + public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection + { + return $this; + } + +} diff --git a/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php index ae33b04ac..fe47fa145 100644 --- a/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php @@ -9,8 +9,6 @@ class UnionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection { - private string $propertyName; - private ?PropertyReflection $transformedProperty = null; private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; @@ -19,11 +17,10 @@ class UnionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedProper * @param UnresolvedPropertyPrototypeReflection[] $propertyPrototypes */ public function __construct( - string $propertyName, + private string $propertyName, private array $propertyPrototypes, ) { - $this->propertyName = $propertyName; } public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index 3ac770e41..900965d8d 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -5,7 +5,8 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Php\EnumPropertyReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\Php\EnumUnresolvedPropertyPrototypeReflection; +use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; @@ -110,15 +111,17 @@ public function tryRemove(Type $typeToRemove): ?Type return null; } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection { $classReflection = $this->getClassReflection(); if ($classReflection === null) { - return parent::getProperty($propertyName, $scope); + return parent::getUnresolvedPropertyPrototype($propertyName, $scope); } if ($propertyName === 'name') { - return new EnumPropertyReflection($classReflection, new ConstantStringType($this->enumCaseName)); + return new EnumUnresolvedPropertyPrototypeReflection( + new EnumPropertyReflection($classReflection, new ConstantStringType($this->enumCaseName)), + ); } if ($classReflection->isBackedEnum() && $propertyName === 'value') { @@ -129,11 +132,13 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco throw new ShouldNotHappenException(); } - return new EnumPropertyReflection($classReflection, $valueType); + return new EnumUnresolvedPropertyPrototypeReflection( + new EnumPropertyReflection($classReflection, $valueType), + ); } } - return parent::getProperty($propertyName, $scope); + return parent::getUnresolvedPropertyPrototype($propertyName, $scope); } public function getBackingValueType(): ?Type diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 700a81c85..0b22ba777 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -28,7 +28,7 @@ use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection; -use PHPStan\Reflection\Type\UnionTypePropertyReflection; +use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; @@ -155,29 +155,6 @@ public function hasProperty(string $propertyName): TrinaryLogic public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection { - $classReflection = $this->getClassReflection(); - if ($classReflection !== null) { - if ($classReflection->isEnum()) { - if ( - $propertyName === 'name' - || ($propertyName === 'value' && $classReflection->isBackedEnum()) - ) { - $properties = []; - foreach ($this->getEnumCases() as $enumCase) { - $properties[] = $enumCase->getProperty($propertyName, $scope); - } - - if (count($properties) > 0) { - if (count($properties) === 1) { - return $properties[0]; - } - - return new UnionTypePropertyReflection($properties); - } - } - } - } - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } @@ -199,6 +176,26 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember throw new ClassNotFoundException($this->className); } + if ($nakedClassReflection->isEnum()) { + if ( + $propertyName === 'name' + || ($propertyName === 'value' && $nakedClassReflection->isBackedEnum()) + ) { + $properties = []; + foreach ($this->getEnumCases() as $enumCase) { + $properties[] = $enumCase->getUnresolvedPropertyPrototype($propertyName, $scope); + } + + if (count($properties) > 0) { + if (count($properties) === 1) { + return $properties[0]; + } + + return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $properties); + } + } + } + if (!$nakedClassReflection->hasNativeProperty($propertyName)) { $nakedClassReflection = $this->getClassReflection(); } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index efe62f843..ce6d5c6a5 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -12,7 +12,6 @@ use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection; -use PHPStan\Reflection\Type\UnionTypePropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; @@ -21,7 +20,6 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; -use function count; use function get_class; use function sprintf; @@ -220,27 +218,6 @@ public function hasProperty(string $propertyName): TrinaryLogic public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection { - $classReflection = $this->getClassReflection(); - if ($classReflection->isEnum()) { - if ( - $propertyName === 'name' - || ($propertyName === 'value' && $classReflection->isBackedEnum()) - ) { - $properties = []; - foreach ($this->getEnumCases() as $enumCase) { - $properties[] = $enumCase->getProperty($propertyName, $scope); - } - - if (count($properties) > 0) { - if (count($properties) === 1) { - return $properties[0]; - } - - return new UnionTypePropertyReflection($properties); - } - } - } - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 820990721..cc0ae6660 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1209,6 +1209,7 @@ public function dataFileAsserts(): iterable if (PHP_VERSION_ID >= 80100) { yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8486.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9000.php'); } yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8956.php'); diff --git a/tests/PHPStan/Analyser/data/bug-9000.php b/tests/PHPStan/Analyser/data/bug-9000.php new file mode 100644 index 000000000..281a6156b --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-9000.php @@ -0,0 +1,31 @@ += 8.1 + +namespace Bug9000; + +use function PHPStan\Testing\assertType; + +enum A:string { + case A = "A"; + case B = "B"; + case C = "C"; +} + +const A_ARRAY = [ + 'A' => A::A, + 'B' => A::B, +]; + +/** + * @param string $key + * @return value-of + */ +function testA(string $key): A +{ + return A_ARRAY[$key]; +} + +function (): void { + $test = testA('A'); + assertType('Bug9000\A::A|Bug9000\A::B', $test); + assertType("'A'|'B'", $test->value); +};