|
2 | 2 |
|
3 | 3 | namespace PHPStan\PhpDoc;
|
4 | 4 |
|
| 5 | +use Nette\Utils\Strings; |
5 | 6 | use PHPStan\Analyser\NameScope;
|
6 | 7 | use PHPStan\DependencyInjection\Container;
|
| 8 | +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; |
| 9 | +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; |
| 10 | +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode; |
7 | 11 | use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
|
| 12 | +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNullNode; |
8 | 13 | use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
|
| 14 | +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode; |
| 15 | +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; |
9 | 16 | use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
|
10 | 17 | use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
11 | 18 | use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
|
12 | 19 | use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
|
| 20 | +use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; |
13 | 21 | use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
14 | 22 | use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
15 | 23 | use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
|
|
30 | 38 | use PHPStan\Type\Constant\ConstantBooleanType;
|
31 | 39 | use PHPStan\Type\Constant\ConstantIntegerType;
|
32 | 40 | use PHPStan\Type\Constant\ConstantStringType;
|
| 41 | +use PHPStan\Type\ConstantTypeHelper; |
33 | 42 | use PHPStan\Type\ErrorType;
|
34 | 43 | use PHPStan\Type\FloatType;
|
35 | 44 | use PHPStan\Type\Generic\GenericClassStringType;
|
@@ -105,6 +114,8 @@ public function resolve(TypeNode $typeNode, NameScope $nameScope): Type
|
105 | 114 |
|
106 | 115 | } elseif ($typeNode instanceof ArrayShapeNode) {
|
107 | 116 | return $this->resolveArrayShapeNode($typeNode, $nameScope);
|
| 117 | + } elseif ($typeNode instanceof ConstTypeNode) { |
| 118 | + return $this->resolveConstTypeNode($typeNode, $nameScope); |
108 | 119 | }
|
109 | 120 |
|
110 | 121 | return new ErrorType();
|
@@ -481,6 +492,97 @@ private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $name
|
481 | 492 | return TypeCombinator::union(...$arrays);
|
482 | 493 | }
|
483 | 494 |
|
| 495 | + private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameScope): Type |
| 496 | + { |
| 497 | + $constExpr = $typeNode->constExpr; |
| 498 | + if ($constExpr instanceof ConstExprArrayNode) { |
| 499 | + throw new \PHPStan\ShouldNotHappenException(); // we prefer array shapes |
| 500 | + } |
| 501 | + |
| 502 | + if ( |
| 503 | + $constExpr instanceof ConstExprFalseNode |
| 504 | + || $constExpr instanceof ConstExprTrueNode |
| 505 | + || $constExpr instanceof ConstExprNullNode |
| 506 | + ) { |
| 507 | + throw new \PHPStan\ShouldNotHappenException(); // we prefer IdentifierTypeNode |
| 508 | + } |
| 509 | + |
| 510 | + if ($constExpr instanceof ConstFetchNode) { |
| 511 | + if ($constExpr->className === '') { |
| 512 | + throw new \PHPStan\ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode |
| 513 | + } |
| 514 | + |
| 515 | + if ($nameScope->getClassName() !== null) { |
| 516 | + switch (strtolower($constExpr->className)) { |
| 517 | + case 'static': |
| 518 | + case 'self': |
| 519 | + $className = $nameScope->getClassName(); |
| 520 | + break; |
| 521 | + |
| 522 | + case 'parent': |
| 523 | + if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { |
| 524 | + $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); |
| 525 | + if ($classReflection->getParentClass() === false) { |
| 526 | + return new ErrorType(); |
| 527 | + |
| 528 | + } |
| 529 | + |
| 530 | + $className = $classReflection->getParentClass()->getName(); |
| 531 | + } |
| 532 | + } |
| 533 | + } |
| 534 | + |
| 535 | + if (!isset($className)) { |
| 536 | + $className = $nameScope->resolveStringName($constExpr->className); |
| 537 | + } |
| 538 | + |
| 539 | + if (!$this->getReflectionProvider()->hasClass($className)) { |
| 540 | + return new ErrorType(); |
| 541 | + } |
| 542 | + |
| 543 | + $classReflection = $this->getReflectionProvider()->getClass($className); |
| 544 | + |
| 545 | + $constantName = $constExpr->name; |
| 546 | + if (Strings::endsWith($constantName, '*')) { |
| 547 | + $constantNameStartsWith = Strings::substring($constantName, 0, Strings::length($constantName) - 1); |
| 548 | + $constantTypes = []; |
| 549 | + foreach ($classReflection->getNativeReflection()->getConstants() as $classConstantName => $constantValue) { |
| 550 | + if (!Strings::startsWith($classConstantName, $constantNameStartsWith)) { |
| 551 | + continue; |
| 552 | + } |
| 553 | + |
| 554 | + $constantTypes[] = ConstantTypeHelper::getTypeFromValue($constantValue); |
| 555 | + } |
| 556 | + |
| 557 | + if (count($constantTypes) === 0) { |
| 558 | + return new ErrorType(); |
| 559 | + } |
| 560 | + |
| 561 | + return TypeCombinator::union(...$constantTypes); |
| 562 | + } |
| 563 | + |
| 564 | + if (!$classReflection->hasConstant($constantName)) { |
| 565 | + return new ErrorType(); |
| 566 | + } |
| 567 | + |
| 568 | + return $classReflection->getConstant($constantName)->getValueType(); |
| 569 | + } |
| 570 | + |
| 571 | + if ($constExpr instanceof ConstExprFloatNode) { |
| 572 | + return ConstantTypeHelper::getTypeFromValue((float) $constExpr->value); |
| 573 | + } |
| 574 | + |
| 575 | + if ($constExpr instanceof ConstExprIntegerNode) { |
| 576 | + return ConstantTypeHelper::getTypeFromValue((int) $constExpr->value); |
| 577 | + } |
| 578 | + |
| 579 | + if ($constExpr instanceof ConstExprStringNode) { |
| 580 | + return ConstantTypeHelper::getTypeFromValue($constExpr->value); |
| 581 | + } |
| 582 | + |
| 583 | + return new ErrorType(); |
| 584 | + } |
| 585 | + |
484 | 586 | /**
|
485 | 587 | * @param TypeNode[] $typeNodes
|
486 | 588 | * @param NameScope $nameScope
|
|
0 commit comments