Skip to content

Commit

Permalink
Fix ->value on unions of enums
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 6, 2023
1 parent 9a598b2 commit 05b85ba
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 57 deletions.
36 changes: 36 additions & 0 deletions src/Reflection/Php/EnumUnresolvedPropertyPrototypeReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection\Php;

use PHPStan\Reflection\PropertyReflection;
use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
use PHPStan\Type\Type;

class EnumUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection
{

public function __construct(private EnumPropertyReflection $property)
{
}

public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototypeReflection
{
return $this;
}

public function getNakedProperty(): PropertyReflection
{
return $this->property;
}

public function getTransformedProperty(): PropertyReflection
{
return $this->property;
}

public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflection
{
return $this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
class UnionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection
{

private string $propertyName;

private ?PropertyReflection $transformedProperty = null;

private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null;
Expand All @@ -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
Expand Down
17 changes: 11 additions & 6 deletions src/Type/Enum/EnumCaseObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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') {
Expand All @@ -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
Expand Down
45 changes: 21 additions & 24 deletions src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}

Expand All @@ -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();
}
Expand Down
23 changes: 0 additions & 23 deletions src/Type/StaticType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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();
}

Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
31 changes: 31 additions & 0 deletions tests/PHPStan/Analyser/data/bug-9000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php // lint >= 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<A_ARRAY>
*/
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);
};

0 comments on commit 05b85ba

Please sign in to comment.