Skip to content

Commit

Permalink
Merge pull request #807 from kukulich/cleanup
Browse files Browse the repository at this point in the history
The resolving of constant name and constant value is now always done in CompileNodeToValue
  • Loading branch information
Ocramius authored Oct 14, 2021
2 parents e1aebd9 + b8099f7 commit 82f4d67
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 233 deletions.
149 changes: 75 additions & 74 deletions src/NodeCompiler/CompileNodeToValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,47 @@
use function constant;
use function defined;
use function dirname;
use function explode;
use function in_array;
use function realpath;
use function sprintf;

/**
* @internal
*/
class CompileNodeToValue
{
/**
* Compile an expression from a node into a value.
*
* @param Node\Stmt\Expression|Node\Expr $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
*
* @return scalar|array<scalar>|null
*
* @throws Exception\UnableToCompileNode
*/
public function __invoke(Node $node, CompilerContext $context): string|int|float|bool|array|null
public function __invoke(Node $node, CompilerContext $context): CompiledValue
{
if ($node instanceof Node\Stmt\Expression) {
return $this($node->expr, $context);
}

$constExprEvaluator = new ConstExprEvaluator(function (Node\Expr $node) use ($context) {
$constantName = null;

if (
$node instanceof Node\Expr\ConstFetch
&& ! in_array($node->name->toLowerString(), ['true', 'false', 'null'], true)
) {
$constantName = $this->resolveConstantName($node, $context);
} elseif ($node instanceof Node\Expr\ClassConstFetch) {
$constantName = $this->resolveClassConstantName($node, $context);
}

$constExprEvaluator = new ConstExprEvaluator(function (Node\Expr $node) use ($context, $constantName): string|int|float|bool|array|null {
if ($node instanceof Node\Expr\ConstFetch) {
return $this->compileConstFetch($node, $context);
return $this->getConstantValue($node, $constantName, $context);
}

if ($node instanceof Node\Expr\ClassConstFetch) {
return $this->compileClassConstFetch($node, $context);
return $this->getClassConstantValue($node, $constantName, $context);
}

if ($node instanceof Node\Scalar\MagicConst\Dir) {
Expand Down Expand Up @@ -96,97 +110,84 @@ public function __invoke(Node $node, CompilerContext $context): string|int|float
throw Exception\UnableToCompileNode::forUnRecognizedExpressionInContext($node, $context);
});

return $constExprEvaluator->evaluateDirectly($node);
$value = $constExprEvaluator->evaluateDirectly($node);

return new CompiledValue($value, $constantName);
}

/**
* Compile constant expressions
*
* @return scalar|array<scalar>|null
*
* @throws Exception\UnableToCompileNode
*/
private function compileConstFetch(Node\Expr\ConstFetch $constNode, CompilerContext $context): string|int|float|bool|array|null
private function resolveConstantName(Node\Expr\ConstFetch $constNode, CompilerContext $context): string
{
switch ($constNode->name->toLowerString()) {
case 'null':
return null;
$constantName = $constNode->name->toString();

case 'false':
return false;
if ($context->getNamespace() !== null && $constNode->name->isUnqualified()) {
$namespacedConstantName = sprintf('%s\\%s', $context->getNamespace(), $constantName);

case 'true':
return true;
if ($this->constantExists($namespacedConstantName, $context)) {
return $namespacedConstantName;
}
}

default:
$constantName = $constNode->name->toString();
if ($this->constantExists($constantName, $context)) {
return $constantName;
}

if ($context->getNamespace() !== null && $constNode->name->isUnqualified()) {
$namespacedConstantName = sprintf('%s\\%s', $context->getNamespace(), $constantName);
throw Exception\UnableToCompileNode::becauseOfNotFoundConstantReference($context, $constNode, $constantName);
}

try {
return $this->getConstantValue($namespacedConstantName, $context);
} catch (IdentifierNotFound) {
// Try constant name without namespace
}
}
private function constantExists(string $constantName, CompilerContext $context): bool
{
if (defined($constantName)) {
return true;
}

try {
return $this->getConstantValue($constantName, $context);
} catch (IdentifierNotFound) {
throw Exception\UnableToCompileNode::becauseOfNotFoundConstantReference($context, $constNode, $constantName);
}
try {
$context->getReflector()->reflectConstant($constantName);

return true;
} catch (IdentifierNotFound) {
return false;
}
}

private function getConstantValue(string $constantName, CompilerContext $context): mixed
private function getConstantValue(Node\Expr\ConstFetch $node, ?string $constantName, CompilerContext $context): mixed
{
// It's not resolved when constant value is expression
$constantName ??= $this->resolveConstantName($node, $context);

if (defined($constantName)) {
return constant($constantName);
}

return $context->getReflector()->reflectConstant($constantName)->getValue();
}

/**
* Compile class constants
*
* @return scalar|array<scalar>|null
*
* @throws IdentifierNotFound
* @throws Exception\UnableToCompileNode If a referenced constant could not be located on the expected referenced class.
*/
private function compileClassConstFetch(Node\Expr\ClassConstFetch $node, CompilerContext $context): string|int|float|bool|array|null
private function resolveClassConstantName(Node\Expr\ClassConstFetch $node, CompilerContext $context): string
{
assert($node->name instanceof Node\Identifier);
$nodeName = $node->name->name;
$constantName = $node->name->name;
assert($node->class instanceof Node\Name);
$className = $node->class->toString();

if ($nodeName === 'class') {
return $this->resolveClassNameForClassNameConstant($className, $context);
}
return sprintf('%s::%s', $this->resolveClassName($className, $context), $constantName);
}

private function getClassConstantValue(Node\Expr\ClassConstFetch $node, ?string $classConstantName, CompilerContext $context): mixed
{
// It's not resolved when constant value is expression
$classConstantName ??= $this->resolveClassConstantName($node, $context);

$classInfo = null;
[$className, $constantName] = explode('::', $classConstantName);

if ($className === 'self' || $className === 'static') {
$classContext = $context->getClass();
assert($classContext !== null);
$classInfo = $classContext->hasConstant($nodeName) ? $classContext : null;
} elseif ($className === 'parent') {
$classContext = $context->getClass();
assert($classContext !== null);
$classInfo = $classContext->getParentClass();
if ($constantName === 'class') {
return $className;
}

if ($classInfo === null) {
$classInfo = $context->getReflector()->reflectClass($className);
}
$classReflection = $context->getReflector()->reflectClass($className);

$reflectionConstant = $classInfo->getReflectionConstant($nodeName);
$reflectionConstant = $classReflection->getReflectionConstant($constantName);

if (! $reflectionConstant instanceof ReflectionClassConstant) {
throw Exception\UnableToCompileNode::becauseOfNotFoundClassConstantReference($context, $classInfo, $node);
throw Exception\UnableToCompileNode::becauseOfNotFoundClassConstantReference($context, $classReflection, $node);
}

return $reflectionConstant->getValue();
Expand Down Expand Up @@ -216,22 +217,22 @@ private function compileClassConstant(CompilerContext $context): string
return $context->getClass()?->getName() ?? '';
}

private function resolveClassNameForClassNameConstant(string $className, CompilerContext $context): string
private function resolveClassName(string $className, CompilerContext $context): string
{
if ($className !== 'self' && $className !== 'static' && $className !== 'parent') {
return $className;
}

$classContext = $context->getClass();
assert($classContext !== null);

if ($className === 'self' || $className === 'static') {
if ($className !== 'parent') {
return $classContext->getName();
}

if ($className === 'parent') {
$parentClass = $classContext->getParentClass();
assert($parentClass instanceof ReflectionClass);

return $parentClass->getName();
}
$parentClass = $classContext->getParentClass();
assert($parentClass instanceof ReflectionClass);

return $className;
return $parentClass->getName();
}
}
18 changes: 18 additions & 0 deletions src/NodeCompiler/CompiledValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Roave\BetterReflection\NodeCompiler;

/**
* @internal
*/
class CompiledValue
{
/**
* @param scalar|array<scalar>|null $value
*/
public function __construct(public string|int|float|bool|array|null $value, public ?string $constantName = null)
{
}
}
3 changes: 3 additions & 0 deletions src/NodeCompiler/Exception/UnableToCompileNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
use function assert;
use function sprintf;

/**
* @internal
*/
class UnableToCompileNode extends LogicException
{
private ?string $constantName = null;
Expand Down
21 changes: 8 additions & 13 deletions src/Reflection/ReflectionClassConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PhpParser\Node\Stmt\ClassConst;
use ReflectionProperty;
use Roave\BetterReflection\NodeCompiler\CompiledValue;
use Roave\BetterReflection\NodeCompiler\CompileNodeToValue;
use Roave\BetterReflection\NodeCompiler\CompilerContext;
use Roave\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
Expand All @@ -16,10 +17,7 @@

class ReflectionClassConstant
{
private bool $valueWasCached = false;

/** @var scalar|array<scalar>|null const value */
private string|int|float|bool|array|null $value = null;
private ?CompiledValue $compiledValue = null;

private Reflector $reflector;

Expand Down Expand Up @@ -72,17 +70,14 @@ public function getName(): string
*/
public function getValue(): string|int|float|bool|array|null
{
if ($this->valueWasCached !== false) {
return $this->value;
if ($this->compiledValue === null) {
$this->compiledValue = (new CompileNodeToValue())->__invoke(
$this->node->consts[$this->positionInNode]->value,
new CompilerContext($this->reflector, $this),
);
}

$this->value = (new CompileNodeToValue())->__invoke(
$this->node->consts[$this->positionInNode]->value,
new CompilerContext($this->reflector, $this),
);
$this->valueWasCached = true;

return $this->value;
return $this->compiledValue->value;
}

/**
Expand Down
15 changes: 6 additions & 9 deletions src/Reflection/ReflectionConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PhpParser\Node;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\NodeCompiler\CompiledValue;
use Roave\BetterReflection\NodeCompiler\CompileNodeToValue;
use Roave\BetterReflection\NodeCompiler\CompilerContext;
use Roave\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
Expand Down Expand Up @@ -38,10 +39,7 @@ class ReflectionConstant implements Reflection

private ?int $positionInNode;

/** @var scalar|array<scalar>|null const value */
private string|int|float|bool|array|null $value = null;

private bool $valueWasCached = false;
private ?CompiledValue $compiledValue = null;

private function __construct()
{
Expand Down Expand Up @@ -212,22 +210,21 @@ public function isDeprecated(): bool
*/
public function getValue(): string|int|float|bool|array|null
{
if ($this->valueWasCached !== false) {
return $this->value;
if ($this->compiledValue !== null) {
return $this->compiledValue->value;
}

/** @psalm-suppress PossiblyNullArrayOffset */
$valueNode = $this->node instanceof Node\Expr\FuncCall
? $this->node->args[1]->value
: $this->node->consts[$this->positionInNode]->value;

$this->value = (new CompileNodeToValue())->__invoke(
$this->compiledValue = (new CompileNodeToValue())->__invoke(
$valueNode,
new CompilerContext($this->reflector, $this),
);
$this->valueWasCached = true;

return $this->value;
return $this->compiledValue->value;
}

public function getFileName(): ?string
Expand Down
Loading

0 comments on commit 82f4d67

Please sign in to comment.