From 3e51899dd7ed0e2785846f8ec820b4cd8214b993 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 13:26:38 +0200 Subject: [PATCH] ConstExprNodeResolver - support ConstFetchNode for class constants --- src/PhpDoc/ConstExprNodeResolver.php | 80 +++++++++++++++++++++++++--- src/PhpDoc/PhpDocNodeResolver.php | 2 +- src/PhpDoc/TypeNodeResolver.php | 1 + 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/PhpDoc/ConstExprNodeResolver.php b/src/PhpDoc/ConstExprNodeResolver.php index 9e3e86f187..257883af2c 100644 --- a/src/PhpDoc/ConstExprNodeResolver.php +++ b/src/PhpDoc/ConstExprNodeResolver.php @@ -2,6 +2,7 @@ namespace PHPStan\PhpDoc; +use PHPStan\Analyser\NameScope; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode; @@ -10,22 +11,35 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNullNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; +use PHPStan\Reflection\InitializerExprContext; +use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\MixedType; +use PHPStan\Type\Enum\EnumCaseObjectType; +use PHPStan\Type\ErrorType; use PHPStan\Type\NullType; use PHPStan\Type\Type; +use function strtolower; final class ConstExprNodeResolver { - public function resolve(ConstExprNode $node): Type + public function __construct( + private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, + private InitializerExprTypeResolver $initializerExprTypeResolver, + ) + { + } + + public function resolve(ConstExprNode $node, NameScope $nameScope): Type { if ($node instanceof ConstExprArrayNode) { - return $this->resolveArrayNode($node); + return $this->resolveArrayNode($node, $nameScope); } if ($node instanceof ConstExprFalseNode) { @@ -52,22 +66,74 @@ public function resolve(ConstExprNode $node): Type return new ConstantStringType($node->value); } - return new MixedType(); + if ($node instanceof ConstFetchNode) { + if ($nameScope->getClassName() !== null) { + switch (strtolower($node->className)) { + case 'static': + case 'self': + $className = $nameScope->getClassName(); + break; + + case 'parent': + if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { + $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); + if ($classReflection->getParentClass() === null) { + return new ErrorType(); + + } + + $className = $classReflection->getParentClass()->getName(); + } + break; + } + } + if (!isset($className)) { + $className = $nameScope->resolveStringName($node->className); + } + if (!$this->getReflectionProvider()->hasClass($className)) { + return new ErrorType(); + } + $classReflection = $this->getReflectionProvider()->getClass($className); + if (!$classReflection->hasConstant($node->name)) { + return new ErrorType(); + } + if ($classReflection->isEnum() && $classReflection->hasEnumCase($node->name)) { + return new EnumCaseObjectType($classReflection->getName(), $node->name); + } + + $reflectionConstant = $classReflection->getNativeReflection()->getReflectionConstant($node->name); + if ($reflectionConstant === false) { + return new ErrorType(); + } + $declaringClass = $reflectionConstant->getDeclaringClass(); + + return $this->initializerExprTypeResolver->getType( + $reflectionConstant->getValueExpression(), + InitializerExprContext::fromClass($declaringClass->getName(), $declaringClass->getFileName() ?: null), + ); + } + + return new ErrorType(); } - private function resolveArrayNode(ConstExprArrayNode $node): Type + private function resolveArrayNode(ConstExprArrayNode $node, NameScope $nameScope): Type { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); foreach ($node->items as $item) { if ($item->key === null) { $key = null; } else { - $key = $this->resolve($item->key); + $key = $this->resolve($item->key, $nameScope); } - $arrayBuilder->setOffsetValueType($key, $this->resolve($item->value)); + $arrayBuilder->setOffsetValueType($key, $this->resolve($item->value, $nameScope)); } return $arrayBuilder->getArray(); } + private function getReflectionProvider(): ReflectionProvider + { + return $this->reflectionProviderProvider->getReflectionProvider(); + } + } diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 58a63f6e85..402f8c7dd1 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -196,7 +196,7 @@ public function resolveMethodTags(PhpDocNode $phpDocNode, NameScope $nameScope): } $defaultValue = null; if ($parameterNode->defaultValue !== null) { - $defaultValue = $this->constExprNodeResolver->resolve($parameterNode->defaultValue); + $defaultValue = $this->constExprNodeResolver->resolve($parameterNode->defaultValue, $nameScope); } $parameters[$parameterName] = new MethodTagParameter( diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index a52fde51f6..c0fa6c5585 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -1059,6 +1059,7 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc $className = $classReflection->getParentClass()->getName(); } + break; } }