diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 368f01a098..52f3b515e3 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -431,7 +431,7 @@ public function resolveConstantType(string $constantName, Type $constantType): T return $constantType; } - public function resolveClassConstantType(string $className, string $constantName, Type $constantType, ?Type $nativeType): Type + public function resolveClassConstantType(string $className, string $constantName, Type $constantType, ?Type $nativeType, ?Type $phpDocType): Type { $lookupConstantName = sprintf('%s::%s', $className, $constantName); if (array_key_exists($lookupConstantName, $this->dynamicConstantNames)) { @@ -454,6 +454,10 @@ public function resolveClassConstantType(string $className, string $constantName return $nativeType; } + if ($phpDocType !== null) { + return $phpDocType; + } + if ($constantType->isConstantValue()->yes()) { return $constantType->generalize(GeneralizePrecision::lessSpecific()); } diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index b4578930ac..de9649776c 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2544,11 +2544,13 @@ function (Type $type, callable $traverse): Type { if ($reflectionConstant->getType() !== null) { $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $constantClassReflection); } + $phpDocType = $constantClassReflection->getConstant($constantName)->getPhpDocType(); $types[] = $this->constantResolver->resolveClassConstantType( $constantClassReflection->getName(), $constantName, $constantType, $nativeType, + $phpDocType, ); unset($this->currentlyResolvingClassConstant[$resolvingName]); continue; @@ -2577,6 +2579,7 @@ function (Type $type, callable $traverse): Type { $constantName, $constantType, $nativeType, + $constantReflection->getPhpDocType(), ); unset($this->currentlyResolvingClassConstant[$resolvingName]); $types[] = $constantType; diff --git a/tests/PHPStan/Analyser/data/dynamic-constant.php b/tests/PHPStan/Analyser/data/dynamic-constant.php index eb42e33c53..7509236df6 100644 --- a/tests/PHPStan/Analyser/data/dynamic-constant.php +++ b/tests/PHPStan/Analyser/data/dynamic-constant.php @@ -13,6 +13,15 @@ class DynamicConstantClass const DYNAMIC_CONSTANT_IN_CLASS = 'abcdef'; const DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES_IN_CLASS = 'xyz'; const PURE_CONSTANT_IN_CLASS = 'abc123def'; + + /** @var string|null */ + const DYNAMIC_NULL_WITH_PHPDOC_CONSTANT = null; + + /** @var list */ + const DYNAMIC_EMPTY_ARRAY_WITH_PHPDOC_CONSTANT = []; + + /** @var int */ + const DYNAMIC_INCOMPATIBLE_PHPDOC_CONSTANT = null; } class NoDynamicConstantClass @@ -29,5 +38,8 @@ private function rip() assertType('bool', GLOBAL_DYNAMIC_CONSTANT); assertType('123', GLOBAL_PURE_CONSTANT); assertType('string|null', GLOBAL_DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES); + assertType('string|null', DynamicConstantClass::DYNAMIC_NULL_WITH_PHPDOC_CONSTANT); + assertType('list', DynamicConstantClass::DYNAMIC_EMPTY_ARRAY_WITH_PHPDOC_CONSTANT); + assertType('int', DynamicConstantClass::DYNAMIC_INCOMPATIBLE_PHPDOC_CONSTANT); } } diff --git a/tests/PHPStan/Analyser/dynamic-constants.neon b/tests/PHPStan/Analyser/dynamic-constants.neon index 43dfa16677..2dfb2167af 100644 --- a/tests/PHPStan/Analyser/dynamic-constants.neon +++ b/tests/PHPStan/Analyser/dynamic-constants.neon @@ -4,6 +4,9 @@ includes: parameters: dynamicConstantNames: - DynamicConstants\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS + - DynamicConstants\DynamicConstantClass::DYNAMIC_NULL_WITH_PHPDOC_CONSTANT + - DynamicConstants\DynamicConstantClass::DYNAMIC_EMPTY_ARRAY_WITH_PHPDOC_CONSTANT + - DynamicConstants\DynamicConstantClass::DYNAMIC_INCOMPATIBLE_PHPDOC_CONSTANT - GLOBAL_DYNAMIC_CONSTANT DynamicConstants\DynamicConstantClass::DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES_IN_CLASS: 'string|null' GLOBAL_DYNAMIC_CONSTANT_WITH_EXPLICIT_TYPES: 'string|null'