Skip to content

Commit 7d8a318

Browse files
Add ConstFetchNode support in array shape
1 parent 0fbb507 commit 7d8a318

File tree

2 files changed

+100
-71
lines changed

2 files changed

+100
-71
lines changed

src/PhpDoc/TypeNodeResolver.php

Lines changed: 78 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,8 @@ private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $name
10571057
$offsetType = new ConstantStringType($itemNode->keyName->name);
10581058
} elseif ($itemNode->keyName instanceof ConstExprStringNode) {
10591059
$offsetType = new ConstantStringType($itemNode->keyName->value);
1060+
} elseif ($itemNode->keyName instanceof ConstFetchNode) {
1061+
$offsetType = $this->resolveConstFetchNode($itemNode->keyName, $nameScope);
10601062
} elseif ($itemNode->keyName !== null) {
10611063
throw new ShouldNotHappenException('Unsupported key node type: ' . get_class($itemNode->keyName));
10621064
}
@@ -1118,107 +1120,112 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc
11181120
}
11191121

11201122
if ($constExpr instanceof ConstFetchNode) {
1121-
if ($constExpr->className === '') {
1122-
throw new ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode
1123-
}
1123+
return $this->resolveConstFetchNode($constExpr, $nameScope);
1124+
}
11241125

1125-
if ($nameScope->getClassName() !== null) {
1126-
switch (strtolower($constExpr->className)) {
1127-
case 'static':
1128-
case 'self':
1129-
$className = $nameScope->getClassName();
1130-
break;
1126+
if ($constExpr instanceof ConstExprFloatNode) {
1127+
return new ConstantFloatType((float) $constExpr->value);
1128+
}
11311129

1132-
case 'parent':
1133-
if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
1134-
$classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
1135-
if ($classReflection->getParentClass() === null) {
1136-
return new ErrorType();
1130+
if ($constExpr instanceof ConstExprIntegerNode) {
1131+
return new ConstantIntegerType((int) $constExpr->value);
1132+
}
11371133

1138-
}
1134+
if ($constExpr instanceof ConstExprStringNode) {
1135+
return new ConstantStringType($constExpr->value);
1136+
}
11391137

1140-
$className = $classReflection->getParentClass()->getName();
1141-
}
1142-
break;
1143-
}
1144-
}
1138+
return new ErrorType();
1139+
}
11451140

1146-
if (!isset($className)) {
1147-
$className = $nameScope->resolveStringName($constExpr->className);
1148-
}
1141+
private function resolveConstFetchNode(ConstFetchNode $constExpr, NameScope $nameScope): Type
1142+
{
1143+
if ($constExpr->className === '') {
1144+
throw new ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode
1145+
}
11491146

1150-
if (!$this->getReflectionProvider()->hasClass($className)) {
1151-
return new ErrorType();
1152-
}
1147+
if ($nameScope->getClassName() !== null) {
1148+
switch (strtolower($constExpr->className)) {
1149+
case 'static':
1150+
case 'self':
1151+
$className = $nameScope->getClassName();
1152+
break;
11531153

1154-
$classReflection = $this->getReflectionProvider()->getClass($className);
1154+
case 'parent':
1155+
if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
1156+
$classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
1157+
if ($classReflection->getParentClass() === null) {
1158+
return new ErrorType();
11551159

1156-
$constantName = $constExpr->name;
1157-
if (Strings::contains($constantName, '*')) {
1158-
// convert * into .*? and escape everything else so the constants can be matched against the pattern
1159-
$pattern = '{^' . str_replace('\\*', '.*?', preg_quote($constantName)) . '$}D';
1160-
$constantTypes = [];
1161-
foreach ($classReflection->getNativeReflection()->getReflectionConstants() as $reflectionConstant) {
1162-
$classConstantName = $reflectionConstant->getName();
1163-
if (Strings::match($classConstantName, $pattern) === null) {
1164-
continue;
1165-
}
1160+
}
11661161

1167-
if ($classReflection->isEnum() && $classReflection->hasEnumCase($classConstantName)) {
1168-
$constantTypes[] = new EnumCaseObjectType($classReflection->getName(), $classConstantName);
1169-
continue;
1162+
$className = $classReflection->getParentClass()->getName();
11701163
}
1164+
break;
1165+
}
1166+
}
11711167

1172-
$declaringClassName = $reflectionConstant->getDeclaringClass()->getName();
1173-
if (!$this->getReflectionProvider()->hasClass($declaringClassName)) {
1174-
continue;
1175-
}
1168+
if (!isset($className)) {
1169+
$className = $nameScope->resolveStringName($constExpr->className);
1170+
}
11761171

1177-
$constantTypes[] = $this->initializerExprTypeResolver->getType(
1178-
$reflectionConstant->getValueExpression(),
1179-
InitializerExprContext::fromClassReflection(
1180-
$this->getReflectionProvider()->getClass($declaringClassName),
1181-
),
1182-
);
1183-
}
1172+
if (!$this->getReflectionProvider()->hasClass($className)) {
1173+
return new ErrorType();
1174+
}
11841175

1185-
if (count($constantTypes) === 0) {
1186-
return new ErrorType();
1176+
$classReflection = $this->getReflectionProvider()->getClass($className);
1177+
1178+
$constantName = $constExpr->name;
1179+
if (Strings::contains($constantName, '*')) {
1180+
// convert * into .*? and escape everything else so the constants can be matched against the pattern
1181+
$pattern = '{^' . str_replace('\\*', '.*?', preg_quote($constantName)) . '$}D';
1182+
$constantTypes = [];
1183+
foreach ($classReflection->getNativeReflection()->getReflectionConstants() as $reflectionConstant) {
1184+
$classConstantName = $reflectionConstant->getName();
1185+
if (Strings::match($classConstantName, $pattern) === null) {
1186+
continue;
11871187
}
11881188

1189-
return TypeCombinator::union(...$constantTypes);
1190-
}
1189+
if ($classReflection->isEnum() && $classReflection->hasEnumCase($classConstantName)) {
1190+
$constantTypes[] = new EnumCaseObjectType($classReflection->getName(), $classConstantName);
1191+
continue;
1192+
}
11911193

1192-
if (!$classReflection->hasConstant($constantName)) {
1193-
return new ErrorType();
1194-
}
1194+
$declaringClassName = $reflectionConstant->getDeclaringClass()->getName();
1195+
if (!$this->getReflectionProvider()->hasClass($declaringClassName)) {
1196+
continue;
1197+
}
11951198

1196-
if ($classReflection->isEnum() && $classReflection->hasEnumCase($constantName)) {
1197-
return new EnumCaseObjectType($classReflection->getName(), $constantName);
1199+
$constantTypes[] = $this->initializerExprTypeResolver->getType(
1200+
$reflectionConstant->getValueExpression(),
1201+
InitializerExprContext::fromClassReflection(
1202+
$this->getReflectionProvider()->getClass($declaringClassName),
1203+
),
1204+
);
11981205
}
11991206

1200-
$reflectionConstant = $classReflection->getNativeReflection()->getReflectionConstant($constantName);
1201-
if ($reflectionConstant === false) {
1207+
if (count($constantTypes) === 0) {
12021208
return new ErrorType();
12031209
}
1204-
$declaringClass = $reflectionConstant->getDeclaringClass();
12051210

1206-
return $this->initializerExprTypeResolver->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($declaringClass->getName(), $declaringClass->getFileName() ?: null));
1211+
return TypeCombinator::union(...$constantTypes);
12071212
}
12081213

1209-
if ($constExpr instanceof ConstExprFloatNode) {
1210-
return new ConstantFloatType((float) $constExpr->value);
1214+
if (!$classReflection->hasConstant($constantName)) {
1215+
return new ErrorType();
12111216
}
12121217

1213-
if ($constExpr instanceof ConstExprIntegerNode) {
1214-
return new ConstantIntegerType((int) $constExpr->value);
1218+
if ($classReflection->isEnum() && $classReflection->hasEnumCase($constantName)) {
1219+
return new EnumCaseObjectType($classReflection->getName(), $constantName);
12151220
}
12161221

1217-
if ($constExpr instanceof ConstExprStringNode) {
1218-
return new ConstantStringType($constExpr->value);
1222+
$reflectionConstant = $classReflection->getNativeReflection()->getReflectionConstant($constantName);
1223+
if ($reflectionConstant === false) {
1224+
return new ErrorType();
12191225
}
1226+
$declaringClass = $reflectionConstant->getDeclaringClass();
12201227

1221-
return new ErrorType();
1228+
return $this->initializerExprTypeResolver->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($declaringClass->getName(), $declaringClass->getFileName() ?: null));
12221229
}
12231230

12241231
private function resolveOffsetAccessNode(OffsetAccessTypeNode $typeNode, NameScope $nameScope): Type
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Bug6989;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class MyClass
8+
{
9+
public const MY_KEY = 'key';
10+
11+
/**
12+
* @param array{static::MY_KEY: string} $items
13+
*
14+
* @return string
15+
*/
16+
public function myMethod(array $items): array
17+
{
18+
assertType('array{key: string}', $items);
19+
20+
return $items[static::MY_KEY];
21+
}
22+
}

0 commit comments

Comments
 (0)