1010use PHPStan \Analyser \ArgumentsNormalizer ;
1111use PHPStan \Analyser \OutOfClassScope ;
1212use PHPStan \BetterReflection \Reflection \Adapter \ReflectionClass ;
13+ use PHPStan \BetterReflection \Reflection \Adapter \ReflectionClassConstant ;
1314use PHPStan \BetterReflection \Reflection \Adapter \ReflectionEnum ;
1415use PHPStan \BetterReflection \Reflection \Adapter \ReflectionEnumBackedCase ;
1516use PHPStan \BetterReflection \Reflection \Adapter \ReflectionMethod ;
@@ -1272,22 +1273,7 @@ public function getConstant(string $name): ClassConstantReflection
12721273 }
12731274 $ fileName = $ declaringClass ->getFileName ();
12741275 $ phpDocType = null ;
1275- $ currentResolvedPhpDoc = $ this ->stubPhpDocProvider ->findClassConstantPhpDoc (
1276- $ declaringClass ->getName (),
1277- $ name ,
1278- );
1279- if ($ currentResolvedPhpDoc === null ) {
1280- if ($ reflectionConstant ->getDocComment () !== false ) {
1281- $ currentResolvedPhpDoc = $ this ->fileTypeMapper ->getResolvedPhpDoc (
1282- $ fileName ,
1283- $ declaringClass ->getName (),
1284- null ,
1285- null ,
1286- $ reflectionConstant ->getDocComment (),
1287- );
1288- }
1289-
1290- }
1276+ $ currentResolvedPhpDoc = $ this ->findConstantResolvedPhpDoc ($ reflectionConstant );
12911277
12921278 $ nativeType = null ;
12931279 if ($ reflectionConstant ->getType () !== null ) {
@@ -1311,18 +1297,7 @@ public function getConstant(string $name): ClassConstantReflection
13111297 }
13121298 $ isInternal = $ resolvedPhpDoc ->isInternal ();
13131299 $ isFinal = $ resolvedPhpDoc ->isFinal ();
1314- $ varTags = $ resolvedPhpDoc ->getVarTags ();
1315- if (isset ($ varTags [0 ]) && count ($ varTags ) === 1 ) {
1316- $ varTag = $ varTags [0 ];
1317- if ($ varTag ->isExplicit () || $ nativeType === null || $ nativeType ->isSuperTypeOf ($ varTag ->getType ())->yes ()) {
1318- $ phpDocType = TemplateTypeHelper::resolveTemplateTypes (
1319- $ varTag ->getType (),
1320- $ declaringClass ->getActiveTemplateTypeMap (),
1321- $ declaringClass ->getCallSiteVarianceMap (),
1322- TemplateTypeVariance::createInvariant (),
1323- );
1324- }
1325- }
1300+ $ phpDocType = self ::resolveConstantVarPhpDocType ($ resolvedPhpDoc , $ nativeType , $ declaringClass );
13261301 }
13271302
13281303 $ this ->constants [$ name ] = new RealClassClassConstantReflection (
@@ -1342,6 +1317,93 @@ public function getConstant(string $name): ClassConstantReflection
13421317 return $ this ->constants [$ name ];
13431318 }
13441319
1320+ /**
1321+ * Returns the @var PHPDoc type of a class constant, if any.
1322+ * Does not walk ancestors, so it's safe to call from class-constant
1323+ * type resolution (which re-enters via @extends<value-of<...>>).
1324+ *
1325+ * @internal
1326+ */
1327+ public function getConstantPhpDocType (string $ name ): ?Type
1328+ {
1329+ $ reflectionConstant = $ this ->getNativeReflection ()->getReflectionConstant ($ name );
1330+ if ($ reflectionConstant === false ) {
1331+ return null ;
1332+ }
1333+
1334+ $ resolvedPhpDoc = $ this ->findConstantResolvedPhpDoc ($ reflectionConstant );
1335+ if ($ resolvedPhpDoc === null ) {
1336+ return null ;
1337+ }
1338+
1339+ $ nativeType = null ;
1340+ if ($ reflectionConstant ->getType () !== null ) {
1341+ $ nativeType = TypehintHelper::decideTypeFromReflection ($ reflectionConstant ->getType ());
1342+ }
1343+
1344+ $ declaringClassName = $ reflectionConstant ->getDeclaringClass ()->getName ();
1345+ if ($ declaringClassName === $ this ->getName ()) {
1346+ $ declaringClass = $ this ;
1347+ } else {
1348+ $ declaringClass = $ this ->getAncestorWithClassName ($ declaringClassName );
1349+ if ($ declaringClass === null ) {
1350+ return null ;
1351+ }
1352+ }
1353+
1354+ return self ::resolveConstantVarPhpDocType ($ resolvedPhpDoc , $ nativeType , $ declaringClass );
1355+ }
1356+
1357+ private function findConstantResolvedPhpDoc (ReflectionClassConstant $ reflectionConstant ): ?ResolvedPhpDocBlock
1358+ {
1359+ $ declaringClassName = $ reflectionConstant ->getDeclaringClass ()->getName ();
1360+
1361+ $ resolvedPhpDoc = $ this ->stubPhpDocProvider ->findClassConstantPhpDoc (
1362+ $ declaringClassName ,
1363+ $ reflectionConstant ->getName (),
1364+ );
1365+ if ($ resolvedPhpDoc !== null ) {
1366+ return $ resolvedPhpDoc ;
1367+ }
1368+
1369+ $ docComment = $ reflectionConstant ->getDocComment ();
1370+ if ($ docComment === false ) {
1371+ return null ;
1372+ }
1373+
1374+ return $ this ->fileTypeMapper ->getResolvedPhpDoc (
1375+ $ reflectionConstant ->getDeclaringClass ()->getFileName () ?: null ,
1376+ $ declaringClassName ,
1377+ null ,
1378+ null ,
1379+ $ docComment ,
1380+ );
1381+ }
1382+
1383+ private static function resolveConstantVarPhpDocType (
1384+ ResolvedPhpDocBlock $ resolvedPhpDoc ,
1385+ ?Type $ nativeType ,
1386+ self $ declaringClass ,
1387+ ): ?Type
1388+ {
1389+ $ varTags = $ resolvedPhpDoc ->getVarTags ();
1390+ if (!isset ($ varTags [0 ]) || count ($ varTags ) !== 1 ) {
1391+ return null ;
1392+ }
1393+
1394+ $ varTag = $ varTags [0 ];
1395+ if (!$ varTag ->isExplicit () && $ nativeType !== null && !$ nativeType ->isSuperTypeOf ($ varTag ->getType ())->yes ()) {
1396+ return null ;
1397+ }
1398+
1399+ return TemplateTypeHelper::resolveTemplateTypes (
1400+ $ varTag ->getType (),
1401+ $ declaringClass ->getActiveTemplateTypeMap (),
1402+ $ declaringClass ->getCallSiteVarianceMap (),
1403+ TemplateTypeVariance::createInvariant (),
1404+ );
1405+ }
1406+
13451407 public function hasTraitUse (string $ traitName ): bool
13461408 {
13471409 return in_array ($ traitName , $ this ->getTraitNames (), true );
0 commit comments