From 9767538b539848ed995caac1d5f616961e67c481 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Fri, 25 Oct 2024 23:34:27 -0400 Subject: [PATCH 1/5] Allow mixed type dynamic constants. Addresses https://github.com/phpstan/phpstan/issues/7520 Revert "Allow mixed type dynamic constants." This reverts commit 1854985cfcb821cb848b6a35ab3fb54322bcf075. Allow setting types for dynamic constants Unit test fixes Unit test fix 2 Lint fixes Use TypeStringResolver directly Remove old code --- src/Analyser/ConstantResolver.php | 32 +++++++++++++++++-- src/Analyser/ConstantResolverFactory.php | 3 ++ src/Analyser/MutatingScope.php | 5 +++ src/Analyser/Scope.php | 2 ++ .../ValidateIgnoredErrorsExtension.php | 2 +- src/Testing/PHPStanTestCase.php | 2 +- .../DefineConstantTypeSpecifyingExtension.php | 8 ++++- .../Analyser/LegacyNodeScopeResolverTest.php | 16 ++++++++-- .../Analyser/data/dynamic-constant.php | 2 ++ 9 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index cf05925fa1..37b1a9e9f5 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -4,8 +4,10 @@ use PhpParser\Node\Name; use PHPStan\DependencyInjection\AutowiredService; +use PHPStan\DependencyInjection\Container; use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Php\PhpVersion; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; @@ -50,6 +52,9 @@ public function __construct( private array $dynamicConstantNames, private int|array|null $phpVersion, private ComposerPhpVersionFactory $composerPhpVersionFactory, + private ?PhpVersion $composerMinPhpVersion, + private ?PhpVersion $composerMaxPhpVersion, + private ?Container $container, ) { } @@ -404,8 +409,17 @@ private function getMaxPhpVersion(): ?PhpVersion public function resolveConstantType(string $constantName, Type $constantType): Type { - if ($constantType->isConstantValue()->yes() && in_array($constantName, $this->dynamicConstantNames, true)) { - return $constantType->generalize(GeneralizePrecision::lessSpecific()); + if ($constantType->isConstantValue()->yes()) { + if (array_key_exists($constantName, $this->dynamicConstantNames)) { + $phpdocTypes = $this->dynamicConstantNames[$constantName]; + $typeStringResolver = $this->container?->getByType(TypeStringResolver::class); + if ($typeStringResolver !== null) { + return $typeStringResolver->resolve($phpdocTypes, new NameScope(null, [], null)); + } + } + if (in_array($constantName, $this->dynamicConstantNames, true)) { + return $constantType->generalize(GeneralizePrecision::lessSpecific()); + } } return $constantType; @@ -414,6 +428,20 @@ public function resolveConstantType(string $constantName, Type $constantType): T public function resolveClassConstantType(string $className, string $constantName, Type $constantType, ?Type $nativeType): Type { $lookupConstantName = sprintf('%s::%s', $className, $constantName); + if (array_key_exists($lookupConstantName, $this->dynamicConstantNames)) { + if ($nativeType !== null) { + return $nativeType; + } + + if ($constantType->isConstantValue()->yes()) { + $phpdocTypes = $this->dynamicConstantNames[$lookupConstantName]; + $typeStringResolver = $this->container?->getByType(TypeStringResolver::class); + if ($typeStringResolver !== null) { + return $typeStringResolver->resolve($phpdocTypes, new NameScope(null, [], $className)); + } + } + } + if (in_array($lookupConstantName, $this->dynamicConstantNames, true)) { if ($nativeType !== null) { return $nativeType; diff --git a/src/Analyser/ConstantResolverFactory.php b/src/Analyser/ConstantResolverFactory.php index 57a3284f76..9bc8d59c08 100644 --- a/src/Analyser/ConstantResolverFactory.php +++ b/src/Analyser/ConstantResolverFactory.php @@ -27,6 +27,9 @@ public function create(): ConstantResolver $this->container->getParameter('dynamicConstantNames'), $this->container->getParameter('phpVersion'), $composerFactory, + $composerFactory->getMinVersion(), + $composerFactory->getMaxVersion(), + $this->container, ); } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 395626e1ce..4197ac2602 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -6191,6 +6191,11 @@ public function getConstantReflection(Type $typeWithConstant, string $constantNa return $typeWithConstant->getConstant($constantName); } + public function getConstantExplicitTypeFromConfig(string $constantName, Type $constantType): Type + { + return $this->constantResolver->resolveConstantType($constantName, $constantType); + } + /** * @return array */ diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 1134614b2f..a33b06a377 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -81,6 +81,8 @@ public function getMethodReflection(Type $typeWithMethod, string $methodName): ? public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection; + public function getConstantExplicitTypeFromConfig(string $constantName, Type $constantType): Type; + public function getIterableKeyType(Type $iteratee): Type; public function getIterableValueType(Type $iteratee): Type; diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index afa8ee0ad1..94bf9c4d6c 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -69,7 +69,7 @@ public function loadConfiguration(): void ReflectionProviderStaticAccessor::registerInstance($reflectionProvider); PhpVersionStaticAccessor::registerInstance(new PhpVersion(PHP_VERSION_ID)); $composerPhpVersionFactory = new ComposerPhpVersionFactory([]); - $constantResolver = new ConstantResolver($reflectionProviderProvider, [], null, $composerPhpVersionFactory); + $constantResolver = new ConstantResolver($reflectionProviderProvider, [], null, $composerPhpVersionFactory, null, null, null); $phpDocParserConfig = new ParserConfig([]); $ignoredRegexValidator = new IgnoredRegexValidator( diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 7587ac8d05..b90ef876c0 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -140,7 +140,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider $reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider); $composerPhpVersionFactory = $container->getByType(ComposerPhpVersionFactory::class); - $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, null, $composerPhpVersionFactory); + $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, null, $composerPhpVersionFactory, null, null, $container); $initializerExprTypeResolver = new InitializerExprTypeResolver( $constantResolver, diff --git a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php index 7364ff54e9..d71df350c1 100644 --- a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php @@ -52,11 +52,17 @@ public function specifyTypes( return new SpecifiedTypes([], []); } + $valueType = $scope->getType($node->getArgs()[1]->value); + $finalType = $scope->getConstantExplicitTypeFromConfig( + $constantName->getValue(), + $valueType, + ); + return $this->typeSpecifier->create( new Node\Expr\ConstFetch( new Node\Name\FullyQualified($constantName->getValue()), ), - $scope->getType($node->getArgs()[1]->value), + $finalType, TypeSpecifierContext::createTruthy(), $scope, )->setAlwaysOverwriteTypes(); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 13fd91edce..967b49e29b 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -8244,6 +8244,10 @@ public static function dataDynamicConstants(): array 'string', 'DynamicConstants\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', ], + [ + 'string|null', + 'DynamicConstants\DynamicConstantClass::DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES_IN_CLASS', + ], [ "'abc123def'", 'DynamicConstants\DynamicConstantClass::PURE_CONSTANT_IN_CLASS', @@ -8253,13 +8257,17 @@ public static function dataDynamicConstants(): array 'DynamicConstants\NoDynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', ], [ - 'false', + 'bool', 'GLOBAL_DYNAMIC_CONSTANT', ], [ '123', 'GLOBAL_PURE_CONSTANT', ], + [ + 'string|null', + 'GLOBAL_DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES', + ], ]; } @@ -8275,8 +8283,10 @@ public function testDynamicConstants( $expression, 'die', [ - 'DynamicConstants\\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', - 'GLOBAL_DYNAMIC_CONSTANT', + 0 => 'DynamicConstants\\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', + 1 => 'GLOBAL_DYNAMIC_CONSTANT', + 'DynamicConstants\\DynamicConstantClass::DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES_IN_CLASS' => 'string|null', + 'GLOBAL_DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES' => 'string|null', ], ); } diff --git a/tests/PHPStan/Analyser/data/dynamic-constant.php b/tests/PHPStan/Analyser/data/dynamic-constant.php index 2219d7698c..30bcf927bd 100644 --- a/tests/PHPStan/Analyser/data/dynamic-constant.php +++ b/tests/PHPStan/Analyser/data/dynamic-constant.php @@ -4,10 +4,12 @@ define('GLOBAL_PURE_CONSTANT', 123); define('GLOBAL_DYNAMIC_CONSTANT', false); +define('GLOBAL_DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES', null); class DynamicConstantClass { const DYNAMIC_CONSTANT_IN_CLASS = 'abcdef'; + const DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES_IN_CLASS = 'xyz'; const PURE_CONSTANT_IN_CLASS = 'abc123def'; } From 45e3dd5b3f9407fea5d073098555fc59425fc8b9 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Thu, 7 Nov 2024 22:13:30 -0500 Subject: [PATCH 2/5] PHP 7.4 fix --- src/Analyser/ConstantResolver.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 37b1a9e9f5..2e5014e119 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -412,8 +412,8 @@ public function resolveConstantType(string $constantName, Type $constantType): T if ($constantType->isConstantValue()->yes()) { if (array_key_exists($constantName, $this->dynamicConstantNames)) { $phpdocTypes = $this->dynamicConstantNames[$constantName]; - $typeStringResolver = $this->container?->getByType(TypeStringResolver::class); - if ($typeStringResolver !== null) { + if ($this->container !== null) { + $typeStringResolver = $this->container->getByType(TypeStringResolver::class); return $typeStringResolver->resolve($phpdocTypes, new NameScope(null, [], null)); } } @@ -435,8 +435,8 @@ public function resolveClassConstantType(string $className, string $constantName if ($constantType->isConstantValue()->yes()) { $phpdocTypes = $this->dynamicConstantNames[$lookupConstantName]; - $typeStringResolver = $this->container?->getByType(TypeStringResolver::class); - if ($typeStringResolver !== null) { + if ($this->container !== null) { + $typeStringResolver = $this->container->getByType(TypeStringResolver::class); return $typeStringResolver->resolve($phpdocTypes, new NameScope(null, [], $className)); } } From 7e803eec7895259ee570f6418b6e24fadb191473 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Wed, 18 Dec 2024 22:30:27 -0500 Subject: [PATCH 3/5] Fix rebase --- src/Analyser/ConstantResolver.php | 2 -- src/Analyser/ConstantResolverFactory.php | 2 -- src/DependencyInjection/ValidateIgnoredErrorsExtension.php | 2 +- src/Testing/PHPStanTestCase.php | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 2e5014e119..547e250f9b 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -52,8 +52,6 @@ public function __construct( private array $dynamicConstantNames, private int|array|null $phpVersion, private ComposerPhpVersionFactory $composerPhpVersionFactory, - private ?PhpVersion $composerMinPhpVersion, - private ?PhpVersion $composerMaxPhpVersion, private ?Container $container, ) { diff --git a/src/Analyser/ConstantResolverFactory.php b/src/Analyser/ConstantResolverFactory.php index 9bc8d59c08..eae83a5ac6 100644 --- a/src/Analyser/ConstantResolverFactory.php +++ b/src/Analyser/ConstantResolverFactory.php @@ -27,8 +27,6 @@ public function create(): ConstantResolver $this->container->getParameter('dynamicConstantNames'), $this->container->getParameter('phpVersion'), $composerFactory, - $composerFactory->getMinVersion(), - $composerFactory->getMaxVersion(), $this->container, ); } diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index 94bf9c4d6c..b4f16e66f7 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -69,7 +69,7 @@ public function loadConfiguration(): void ReflectionProviderStaticAccessor::registerInstance($reflectionProvider); PhpVersionStaticAccessor::registerInstance(new PhpVersion(PHP_VERSION_ID)); $composerPhpVersionFactory = new ComposerPhpVersionFactory([]); - $constantResolver = new ConstantResolver($reflectionProviderProvider, [], null, $composerPhpVersionFactory, null, null, null); + $constantResolver = new ConstantResolver($reflectionProviderProvider, [], null, $composerPhpVersionFactory, null); $phpDocParserConfig = new ParserConfig([]); $ignoredRegexValidator = new IgnoredRegexValidator( diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index b90ef876c0..295024119b 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -140,7 +140,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider $reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider); $composerPhpVersionFactory = $container->getByType(ComposerPhpVersionFactory::class); - $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, null, $composerPhpVersionFactory, null, null, $container); + $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, null, $composerPhpVersionFactory, $container); $initializerExprTypeResolver = new InitializerExprTypeResolver( $constantResolver, From ed0d74a1fea0b9d3cd4fb977a36f280ddf6fda75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Sat, 30 Aug 2025 16:15:38 +0200 Subject: [PATCH 4/5] Update src/Analyser/ConstantResolver.php --- src/Analyser/ConstantResolver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 547e250f9b..57e0546e38 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -414,6 +414,7 @@ public function resolveConstantType(string $constantName, Type $constantType): T $typeStringResolver = $this->container->getByType(TypeStringResolver::class); return $typeStringResolver->resolve($phpdocTypes, new NameScope(null, [], null)); } + return $constantType; } if (in_array($constantName, $this->dynamicConstantNames, true)) { return $constantType->generalize(GeneralizePrecision::lessSpecific()); From a46679db20250070d5bcfa88a290518ec0cf0bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Sat, 30 Aug 2025 16:15:46 +0200 Subject: [PATCH 5/5] Update src/Analyser/ConstantResolver.php --- src/Analyser/ConstantResolver.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 57e0546e38..bbdfd754c4 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -428,10 +428,6 @@ public function resolveClassConstantType(string $className, string $constantName { $lookupConstantName = sprintf('%s::%s', $className, $constantName); if (array_key_exists($lookupConstantName, $this->dynamicConstantNames)) { - if ($nativeType !== null) { - return $nativeType; - } - if ($constantType->isConstantValue()->yes()) { $phpdocTypes = $this->dynamicConstantNames[$lookupConstantName]; if ($this->container !== null) { @@ -439,6 +435,12 @@ public function resolveClassConstantType(string $className, string $constantName return $typeStringResolver->resolve($phpdocTypes, new NameScope(null, [], $className)); } } + + if ($nativeType !== null) { + return $nativeType; + } + + return $constantType; } if (in_array($lookupConstantName, $this->dynamicConstantNames, true)) {