diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index cf05925fa1..bbdfd754c4 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,7 @@ public function __construct( private array $dynamicConstantNames, private int|array|null $phpVersion, private ComposerPhpVersionFactory $composerPhpVersionFactory, + private ?Container $container, ) { } @@ -404,8 +407,18 @@ 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]; + if ($this->container !== null) { + $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()); + } } return $constantType; @@ -414,6 +427,22 @@ 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 ($constantType->isConstantValue()->yes()) { + $phpdocTypes = $this->dynamicConstantNames[$lookupConstantName]; + if ($this->container !== null) { + $typeStringResolver = $this->container->getByType(TypeStringResolver::class); + return $typeStringResolver->resolve($phpdocTypes, new NameScope(null, [], $className)); + } + } + + if ($nativeType !== null) { + return $nativeType; + } + + return $constantType; + } + 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..eae83a5ac6 100644 --- a/src/Analyser/ConstantResolverFactory.php +++ b/src/Analyser/ConstantResolverFactory.php @@ -27,6 +27,7 @@ public function create(): ConstantResolver $this->container->getParameter('dynamicConstantNames'), $this->container->getParameter('phpVersion'), $composerFactory, + $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..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); + $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 7587ac8d05..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); + $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, null, $composerPhpVersionFactory, $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'; }