Skip to content

Commit

Permalink
Fix value-of for multiple enums
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 6, 2023
1 parent dda95b4 commit 3adc91d
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 22 deletions.
34 changes: 12 additions & 22 deletions src/PhpDoc/TypeNodeResolver.php
Expand Up @@ -628,32 +628,22 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na
return new ErrorType();
} elseif ($mainTypeName === 'value-of') {
if (count($genericTypes) === 1) { // value-of<ValueType>
$genericTypeObjectClassNames = $genericTypes[0]->getObjectClassNames();
if (count($genericTypeObjectClassNames) > 1) {
throw new ShouldNotHappenException();
}

if ($genericTypeObjectClassNames !== []) {
if ($this->getReflectionProvider()->hasClass($genericTypeObjectClassNames[0])) {
$classReflection = $this->getReflectionProvider()->getClass($genericTypeObjectClassNames[0]);

if ($classReflection->isBackedEnum()) {
$cases = [];
foreach ($classReflection->getEnumCases() as $enumCaseReflection) {
$backingType = $enumCaseReflection->getBackingValueType();
if ($backingType === null) {
continue;
}

$cases[] = $backingType;
}

return TypeCombinator::union(...$cases);
$genericType = $genericTypes[0];
if ($genericType->isEnum()->yes()) {
$valueTypes = [];
foreach ($genericType->getEnumCases() as $enumCase) {
$valueType = $enumCase->getBackingValueType();
if ($valueType === null) {
continue;
}

$valueTypes[] = $valueType;
}

return TypeCombinator::union(...$valueTypes);
}

$type = new ValueOfType($genericTypes[0]);
$type = new ValueOfType($genericType);
return $type->isResolvable() ? $type->resolve() : $type;
}

Expand Down
20 changes: 20 additions & 0 deletions src/Type/Enum/EnumCaseObjectType.php
Expand Up @@ -136,6 +136,26 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
return parent::getProperty($propertyName, $scope);
}

public function getBackingValueType(): ?Type
{
$classReflection = $this->getClassReflection();
if ($classReflection === null) {
return null;
}

if (!$classReflection->isBackedEnum()) {
return null;
}

if ($classReflection->hasEnumCase($this->enumCaseName)) {
$enumCase = $classReflection->getEnumCase($this->enumCaseName);

return $enumCase->getBackingValueType();
}

return null;
}

public function generalize(GeneralizePrecision $precision): Type
{
return new parent($this->getClassName(), null, $this->getClassReflection());
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Expand Up @@ -1154,6 +1154,12 @@ public function testSkipCheckNoGenericClasses(): void
$this->assertSame('Method SkipCheckNoGenericClasses\Foo::doFoo() has parameter $i with generic class LimitIterator but does not specify its types: TKey, TValue, TIterator', $errors[0]->getMessage());
}

public function testBug8983(): void
{
$errors = $this->runAnalyse(__DIR__ . '/data/bug-8983.php');
$this->assertNoErrors($errors);
}

/**
* @param string[]|null $allAnalysedFiles
* @return Error[]
Expand Down
30 changes: 30 additions & 0 deletions tests/PHPStan/Analyser/data/bug-8983.php
@@ -0,0 +1,30 @@
<?php

namespace Bug8983;

use function PHPStan\Testing\assertType;

enum Enum1: string
{

case FOO = 'foo';

}

enum Enum2: string
{

case BAR = 'bar';

}

class Foo
{

/** @param value-of<Enum1|Enum2> $bar */
public function doFoo($bar): void
{
assertType("'bar'|'foo'", $bar);
}

}

0 comments on commit 3adc91d

Please sign in to comment.