Skip to content

Commit 54456c5

Browse files
Add ConstFetchNode support in array shape
1 parent 0fbb507 commit 54456c5

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

src/PhpDoc/TypeNodeResolver.php

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
2121
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode;
2222
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
23+
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
2324
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
2425
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
2526
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
@@ -1050,16 +1051,7 @@ private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $name
10501051
}
10511052

10521053
foreach ($typeNode->items as $itemNode) {
1053-
$offsetType = null;
1054-
if ($itemNode->keyName instanceof ConstExprIntegerNode) {
1055-
$offsetType = new ConstantIntegerType((int) $itemNode->keyName->value);
1056-
} elseif ($itemNode->keyName instanceof IdentifierTypeNode) {
1057-
$offsetType = new ConstantStringType($itemNode->keyName->name);
1058-
} elseif ($itemNode->keyName instanceof ConstExprStringNode) {
1059-
$offsetType = new ConstantStringType($itemNode->keyName->value);
1060-
} elseif ($itemNode->keyName !== null) {
1061-
throw new ShouldNotHappenException('Unsupported key node type: ' . get_class($itemNode->keyName));
1062-
}
1054+
$offsetType = $this->resolveArrayShapeOffsetType($itemNode, $nameScope);
10631055
$builder->setOffsetValueType($offsetType, $this->resolve($itemNode->valueType, $nameScope), $itemNode->optional);
10641056
}
10651057

@@ -1081,6 +1073,69 @@ private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $name
10811073
return $arrayType;
10821074
}
10831075

1076+
private function resolveArrayShapeOffsetType(ArrayShapeItemNode $itemNode, NameScope $nameScope): ?Type
1077+
{
1078+
if ($itemNode->keyName instanceof ConstExprIntegerNode) {
1079+
return new ConstantIntegerType((int) $itemNode->keyName->value);
1080+
} elseif ($itemNode->keyName instanceof IdentifierTypeNode) {
1081+
return new ConstantStringType($itemNode->keyName->name);
1082+
} elseif ($itemNode->keyName instanceof ConstExprStringNode) {
1083+
return new ConstantStringType($itemNode->keyName->value);
1084+
} elseif ($itemNode->keyName instanceof ConstFetchNode) {
1085+
$constExpr = $itemNode->keyName;
1086+
if ($constExpr->className === '') {
1087+
throw new ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode
1088+
}
1089+
1090+
if ($nameScope->getClassName() !== null) {
1091+
switch (strtolower($constExpr->className)) {
1092+
case 'static':
1093+
case 'self':
1094+
$className = $nameScope->getClassName();
1095+
break;
1096+
1097+
case 'parent':
1098+
if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
1099+
$classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
1100+
if ($classReflection->getParentClass() === null) {
1101+
return new ErrorType();
1102+
1103+
}
1104+
1105+
$className = $classReflection->getParentClass()->getName();
1106+
}
1107+
break;
1108+
}
1109+
}
1110+
1111+
if (!isset($className)) {
1112+
$className = $nameScope->resolveStringName($constExpr->className);
1113+
}
1114+
1115+
if (!$this->getReflectionProvider()->hasClass($className)) {
1116+
return new ErrorType();
1117+
}
1118+
$classReflection = $this->getReflectionProvider()->getClass($className);
1119+
1120+
$constantName = $constExpr->name;
1121+
if (!$classReflection->hasConstant($constantName)) {
1122+
return new ErrorType();
1123+
}
1124+
1125+
$reflectionConstant = $classReflection->getNativeReflection()->getReflectionConstant($constantName);
1126+
if ($reflectionConstant === false) {
1127+
return new ErrorType();
1128+
}
1129+
$declaringClass = $reflectionConstant->getDeclaringClass();
1130+
1131+
return $this->initializerExprTypeResolver->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($declaringClass->getName(), $declaringClass->getFileName() ?: null));
1132+
} elseif ($itemNode->keyName !== null) {
1133+
throw new ShouldNotHappenException('Unsupported key node type: ' . get_class($itemNode->keyName));
1134+
}
1135+
1136+
return null;
1137+
}
1138+
10841139
private function resolveObjectShapeNode(ObjectShapeNode $typeNode, NameScope $nameScope): Type
10851140
{
10861141
$properties = [];
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)