Skip to content

Commit 2c2f9a2

Browse files
drewmtondrejmirtes
authored andcommitted
Resolve unit enum defaults through generic class reflection
Amp-Thread-ID: https://ampcode.com/threads/T-01a196f7-a83c-7170-a45b-f1d9db4220df
1 parent 92a35f5 commit 2c2f9a2

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

‎src/Reflection/InitializerExprTypeResolver.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2779,6 +2779,11 @@ public function getClassConstFetchTypeByReflection(Name|Expr $class, string $con
27792779
unset($this->currentlyResolvingClassConstant[$resolvingName]);
27802780
continue;
27812781
}
2782+
if ($reflectionConstant->isEnumCase()) {
2783+
$types[] = new EnumCaseObjectType($constantClassReflection->getName(), $constantName);
2784+
unset($this->currentlyResolvingClassConstant[$resolvingName]);
2785+
continue;
2786+
}
27822787
$reflectionConstantDeclaringClass = $reflectionConstant->getDeclaringClass();
27832788
$constantType = $this->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($reflectionConstantDeclaringClass->getName(), $reflectionConstantDeclaringClass->getFileName() ?: null));
27842789
$nativeType = null;

‎tests/PHPStan/Reflection/InitializerExprTypeResolverTest.php‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,61 @@
33
namespace PHPStan\Reflection;
44

55
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Expr\ClassConstFetch;
7+
use PhpParser\Node\Name\FullyQualified;
68
use PhpParser\Node\Scalar\LNumber;
79
use PhpParser\Node\Scalar\String_;
10+
use PHPStan\Analyser\ConstantResolver;
11+
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass;
12+
use PHPStan\Php\PhpVersion;
13+
use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider;
814
use PHPStan\ShouldNotHappenException;
915
use PHPStan\Testing\PHPStanTestCase;
1016
use PHPStan\Type\Constant\ConstantIntegerType;
17+
use PHPStan\Type\Constant\OversizedArrayBuilder;
1118
use PHPStan\Type\NeverType;
19+
use PHPStan\Type\OperatorTypeSpecifyingExtensionRegistry;
1220
use PHPStan\Type\Type;
21+
use PHPStan\Type\UnaryOperatorTypeSpecifyingExtensionRegistry;
22+
use PHPStan\Type\VerbosityLevel;
1323
use PHPUnit\Framework\Attributes\DataProvider;
1424

1525
class InitializerExprTypeResolverTest extends PHPStanTestCase
1626
{
1727

28+
public function testUnitEnumDefaultWithClassAdapter(): void
29+
{
30+
$container = self::getContainer();
31+
$classReflection = $container->getByType(ClassReflectionFactory::class)->create(
32+
'RoundingMode',
33+
new ReflectionClass(self::getReflector()->reflectClass('RoundingMode')),
34+
null,
35+
null,
36+
null,
37+
);
38+
39+
// PHP runtimes without native enum reflection use the generic class adapter.
40+
$reflectionProvider = $this->createMock(ReflectionProvider::class);
41+
$reflectionProvider->method('hasClass')->with('RoundingMode')->willReturn(true);
42+
$reflectionProvider->method('getClass')->with('RoundingMode')->willReturn($classReflection);
43+
$resolver = new InitializerExprTypeResolver(
44+
$container->getByType(ConstantResolver::class),
45+
new DirectReflectionProviderProvider($reflectionProvider),
46+
new PhpVersion(80500),
47+
$container->getByType(OperatorTypeSpecifyingExtensionRegistry::class),
48+
$container->getByType(UnaryOperatorTypeSpecifyingExtensionRegistry::class),
49+
new OversizedArrayBuilder(),
50+
$container->getParameter('usePathConstantsAsConstantString'),
51+
);
52+
53+
$type = $resolver->getType(
54+
new ClassConstFetch(new FullyQualified('RoundingMode'), 'HalfAwayFromZero'),
55+
InitializerExprContext::createEmpty(),
56+
);
57+
58+
$this->assertSame('RoundingMode::HalfAwayFromZero', $type->describe(VerbosityLevel::precise()));
59+
}
60+
1861
public static function dataExplicitNever(): iterable
1962
{
2063
yield [

‎turbo-ext/src/InitializerExprTypeResolver.cpp‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ constexpr Method mGetReflectionConstant{"getReflectionConstant"};
159159
constexpr Method mGetDeclaringClass{"getDeclaringClass"};
160160
constexpr Method mGetFileName{"getFileName", PT_OP_GET_FILE_NAME};
161161
constexpr Method mGetValueExpression{"getValueExpression"};
162+
constexpr Method mIsEnumCase{"isEnumCase"};
162163
constexpr Method mGetType{"getType", PT_OP_GET_TYPE};
163164
constexpr Method mGetConstantPhpDocType{"getConstantPhpDocType"};
164165
constexpr Method mGetConstant{"getConstant"};
@@ -4757,6 +4758,20 @@ class InitializerExprTypeResolver
47574758
unsetArrayProperty(slots::currentlyResolvingClassConstant, resolvingName);
47584759
continue;
47594760
}
4761+
{
4762+
IETR_VAL(isEnumCase, callOn(reflectionConstant.raw(), mIsEnumCase));
4763+
if (zend_is_true(isEnumCase.raw())) {
4764+
if (UNEXPECTED(Z_TYPE_P(reflectionName.raw()) != IS_STRING)) {
4765+
pt_throw_should_not_happen();
4766+
return zv::Val();
4767+
}
4768+
zval enumCase;
4769+
if (UNEXPECTED(!pt_enum_case_object_type_new(&enumCase, Z_STR_P(reflectionName.raw()), constantNameString))) return zv::Val();
4770+
types.push(zv::Val::adopt(enumCase));
4771+
unsetArrayProperty(slots::currentlyResolvingClassConstant, resolvingName);
4772+
continue;
4773+
}
4774+
}
47604775
IETR_VAL(reflectionConstantDeclaringClass, callOn(reflectionConstant.raw(), mGetDeclaringClass));
47614776
IETR_VAL(valueExpression, callOn(reflectionConstant.raw(), mGetValueExpression));
47624777
IETR_VAL(declaringClassName, callOn(reflectionConstantDeclaringClass.raw(), mGetName));

0 commit comments

Comments
 (0)