Skip to content

Commit 08b44e8

Browse files
Add ConstFetchNode support in array shape key
1 parent b13745e commit 08b44e8

File tree

2 files changed

+116
-10
lines changed

2 files changed

+116
-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: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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} $items1
13+
* @param array{self::MY_KEY: string} $items2
14+
* @param array{MyClass::MY_KEY: string} $items3
15+
*
16+
* @return string
17+
*/
18+
public function myMethod(array $items1, array $items2, array $items3): array
19+
{
20+
assertType('array{key: string}', $items1);
21+
assertType('array{key: string}', $items2);
22+
assertType('array{key: string}', $items3);
23+
24+
return $items1[static::MY_KEY];
25+
}
26+
}
27+
28+
class ParentClass extends MyClass
29+
{
30+
public const MY_KEY = 'different_key';
31+
32+
/**
33+
* @param array{static::MY_KEY: string} $items1
34+
* @param array{self::MY_KEY: string} $items2
35+
* @param array{MyClass::MY_KEY: string} $items3
36+
* @param array{ParentClass::MY_KEY: string} $items4
37+
* @param array{parent::MY_KEY: string} $items5
38+
*
39+
* @return string
40+
*/
41+
public function myMethod2(array $items1, array $items2, array $items3, array $items4, array $items5): array
42+
{
43+
assertType('array{different_key: string}', $items1);
44+
assertType('array{different_key: string}', $items2);
45+
assertType('array{key: string}', $items3);
46+
assertType('array{different_key: string}', $items4);
47+
assertType('array{key: string}', $items5);
48+
49+
return $items1[static::MY_KEY];
50+
}
51+
}

0 commit comments

Comments
 (0)