diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index abf5384f2a4..5f6169c36bb 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2779,6 +2779,11 @@ public function getClassConstFetchTypeByReflection(Name|Expr $class, string $con unset($this->currentlyResolvingClassConstant[$resolvingName]); continue; } + if ($reflectionConstant->isEnumCase()) { + $types[] = new EnumCaseObjectType($constantClassReflection->getName(), $constantName); + unset($this->currentlyResolvingClassConstant[$resolvingName]); + continue; + } $reflectionConstantDeclaringClass = $reflectionConstant->getDeclaringClass(); $constantType = $this->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($reflectionConstantDeclaringClass->getName(), $reflectionConstantDeclaringClass->getFileName() ?: null)); $nativeType = null; diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 0daf5224b3d..39f0767632d 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -5,6 +5,9 @@ use Bug4288\MyClass; use Bug4713\Service; use ExtendingKnownClassWithCheck\Foo; +use PHPStan\DependencyInjection\Container; +use PHPStan\DependencyInjection\ContainerFactory; +use PHPStan\File\FileHelper; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; @@ -19,6 +22,7 @@ use function count; use function extension_loaded; use function sprintf; +use function sys_get_temp_dir; use const PHP_VERSION_ID; #[CoversNothing] @@ -1834,16 +1838,32 @@ public function testBug15520(): void } } + public function testBug15380(): void + { + $containerFactory = new ContainerFactory(__DIR__); + $container = $containerFactory->create(sys_get_temp_dir() . '/phpstan-tests', [ + __DIR__ . '/data/bug-15380.neon', + ], []); + + try { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-15380.php', null, $container); + $this->assertNoErrors($errors); + } finally { + self::getContainer(); + } + } + /** * @param string[]|null $allAnalysedFiles * @return list */ - private function runAnalyse(string $file, ?array $allAnalysedFiles = null): array + private function runAnalyse(string $file, ?array $allAnalysedFiles = null, ?Container $container = null): array { - $file = $this->getFileHelper()->normalizePath($file); + $container ??= self::getContainer(); + $file = $container->getByType(FileHelper::class)->normalizePath($file); - $analyser = self::getContainer()->getByType(Analyser::class); - $finalizer = self::getContainer()->getByType(AnalyserResultFinalizer::class); + $analyser = $container->getByType(Analyser::class); + $finalizer = $container->getByType(AnalyserResultFinalizer::class); $errors = $finalizer->finalize( $analyser->analyse([$file], null, null, true, $allAnalysedFiles), false, diff --git a/tests/PHPStan/Analyser/data/bug-15380.neon b/tests/PHPStan/Analyser/data/bug-15380.neon new file mode 100644 index 00000000000..31a9dd9edcd --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-15380.neon @@ -0,0 +1,7 @@ +includes: + - ../../../../conf/config.level8.neon + - ../../../../conf/bleedingEdge.neon + - ../../../../src/Testing/TestCase.neon + +parameters: + phpVersion: 80500 diff --git a/tests/PHPStan/Analyser/data/bug-15380.php b/tests/PHPStan/Analyser/data/bug-15380.php new file mode 100644 index 00000000000..e02586016f1 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-15380.php @@ -0,0 +1,3 @@ +createForNewestSupportedVersion()), + )); + $classReflection = $container->getByType(ClassReflectionFactory::class)->create( + 'RoundingMode', + new ReflectionClass($reflector->reflectClass('RoundingMode')), + null, + null, + null, + ); + + // PHP runtimes without native enum reflection use the generic class adapter. + $reflectionProvider = $this->createMock(ReflectionProvider::class); + $reflectionProvider->method('hasClass')->with('RoundingMode')->willReturn(true); + $reflectionProvider->method('getClass')->with('RoundingMode')->willReturn($classReflection); + $resolver = new InitializerExprTypeResolver( + $container->getByType(ConstantResolver::class), + new DirectReflectionProviderProvider($reflectionProvider), + new PhpVersion(80500), + $container->getByType(OperatorTypeSpecifyingExtensionRegistry::class), + $container->getByType(UnaryOperatorTypeSpecifyingExtensionRegistry::class), + new OversizedArrayBuilder(), + $container->getParameter('usePathConstantsAsConstantString'), + ); + + $type = $resolver->getType( + new ClassConstFetch(new FullyQualified('RoundingMode'), 'HalfAwayFromZero'), + InitializerExprContext::createEmpty(), + ); + + $this->assertSame('RoundingMode::HalfAwayFromZero', $type->describe(VerbosityLevel::precise())); + } + public static function dataExplicitNever(): iterable { yield [ diff --git a/turbo-ext/src/InitializerExprTypeResolver.cpp b/turbo-ext/src/InitializerExprTypeResolver.cpp index a8dccb753ac..dd6052cad5d 100644 --- a/turbo-ext/src/InitializerExprTypeResolver.cpp +++ b/turbo-ext/src/InitializerExprTypeResolver.cpp @@ -159,6 +159,7 @@ constexpr Method mGetReflectionConstant{"getReflectionConstant"}; constexpr Method mGetDeclaringClass{"getDeclaringClass"}; constexpr Method mGetFileName{"getFileName", PT_OP_GET_FILE_NAME}; constexpr Method mGetValueExpression{"getValueExpression"}; +constexpr Method mIsEnumCase{"isEnumCase"}; constexpr Method mGetType{"getType", PT_OP_GET_TYPE}; constexpr Method mGetConstantPhpDocType{"getConstantPhpDocType"}; constexpr Method mGetConstant{"getConstant"}; @@ -4757,6 +4758,20 @@ class InitializerExprTypeResolver unsetArrayProperty(slots::currentlyResolvingClassConstant, resolvingName); continue; } + { + IETR_VAL(isEnumCase, callOn(reflectionConstant.raw(), mIsEnumCase)); + if (zend_is_true(isEnumCase.raw())) { + if (UNEXPECTED(Z_TYPE_P(reflectionName.raw()) != IS_STRING)) { + pt_throw_should_not_happen(); + return zv::Val(); + } + zval enumCase; + if (UNEXPECTED(!pt_enum_case_object_type_new(&enumCase, Z_STR_P(reflectionName.raw()), constantNameString))) return zv::Val(); + types.push(zv::Val::adopt(enumCase)); + unsetArrayProperty(slots::currentlyResolvingClassConstant, resolvingName); + continue; + } + } IETR_VAL(reflectionConstantDeclaringClass, callOn(reflectionConstant.raw(), mGetDeclaringClass)); IETR_VAL(valueExpression, callOn(reflectionConstant.raw(), mGetValueExpression)); IETR_VAL(declaringClassName, callOn(reflectionConstantDeclaringClass.raw(), mGetName));