Skip to content

Commit a0084a5

Browse files
committed
Merge branch 2.1.x into 2.2.x
2 parents 93087a6 + 1bf9426 commit a0084a5

4 files changed

Lines changed: 135 additions & 31 deletions

File tree

src/Reflection/ClassReflection.php

Lines changed: 90 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Analyser\ArgumentsNormalizer;
1111
use PHPStan\Analyser\OutOfClassScope;
1212
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass;
13+
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant;
1314
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum;
1415
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase;
1516
use 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);

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2544,13 +2544,12 @@ function (Type $type, callable $traverse): Type {
25442544
if ($reflectionConstant->getType() !== null) {
25452545
$nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $constantClassReflection);
25462546
}
2547-
$phpDocType = $constantClassReflection->getConstant($constantName)->getPhpDocType();
25482547
$types[] = $this->constantResolver->resolveClassConstantType(
25492548
$constantClassReflection->getName(),
25502549
$constantName,
25512550
$constantType,
25522551
$nativeType,
2553-
$phpDocType,
2552+
$constantClassReflection->getConstantPhpDocType($constantName),
25542553
);
25552554
unset($this->currentlyResolvingClassConstant[$resolvingName]);
25562555
continue;
@@ -2579,7 +2578,7 @@ function (Type $type, callable $traverse): Type {
25792578
$constantName,
25802579
$constantType,
25812580
$nativeType,
2582-
$constantReflection->getPhpDocType(),
2581+
$constantClassReflection->getConstantPhpDocType($constantName),
25832582
);
25842583
unset($this->currentlyResolvingClassConstant[$resolvingName]);
25852584
$types[] = $constantType;

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,13 @@ public function testBugInfiniteLoopOnFileTypeMapper(): void
15361536
$this->assertCount(0, $errors);
15371537
}
15381538

1539+
#[RequiresPhp('>= 8.3.0')]
1540+
public function testBug14501(): void
1541+
{
1542+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-14501.php');
1543+
$this->assertNoErrors($errors);
1544+
}
1545+
15391546
/**
15401547
* @param string[]|null $allAnalysedFiles
15411548
* @return list<Error>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug14501;
4+
5+
function bug14501Trigger(): string
6+
{
7+
return Bug14501Sub::ATTR_A;
8+
}
9+
10+
/**
11+
* @template TAttribute of string
12+
*/
13+
abstract class Bug14501Base
14+
{
15+
/**
16+
* @param TAttribute $attribute
17+
*/
18+
abstract public function check(string $attribute): bool;
19+
}
20+
21+
/** @extends Bug14501Base<value-of<self::ATTRIBUTES>> */
22+
final class Bug14501Sub extends Bug14501Base
23+
{
24+
public const string ATTR_A = 'A';
25+
public const string ATTR_B = 'B';
26+
27+
public const array ATTRIBUTES = [
28+
self::ATTR_A,
29+
self::ATTR_B,
30+
];
31+
32+
public function check(string $attribute): bool
33+
{
34+
return $attribute === self::ATTR_A;
35+
}
36+
}

0 commit comments

Comments
 (0)