Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved node compilation in CompileNodeToValue (compile solution) #95

Merged
merged 10 commits into from
Aug 13, 2015
224 changes: 192 additions & 32 deletions src/NodeCompiler/CompileNodeToValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace BetterReflection\NodeCompiler;

use BetterReflection\Reflection\ReflectionClass;
use PhpParser\Node;

class CompileNodeToValue
Expand All @@ -10,10 +11,11 @@ class CompileNodeToValue
* Compile an expression from a node into a value.
*
* @param Node $node
* @param CompilerContext $context
* @return mixed
* @throw Exception\UnableToCompileNode
*/
public function __invoke(Node $node)
public function __invoke(Node $node, CompilerContext $context)
{
if ($node instanceof Node\Scalar\String_
|| $node instanceof Node\Scalar\DNumber
Expand All @@ -23,48 +25,206 @@ public function __invoke(Node $node)

// common edge case - negative numbers
if ($node instanceof Node\Expr\UnaryMinus) {
return $this->__invoke($node->expr) * -1;
return $this($node->expr, $context) * -1;
}

if ($node instanceof Node\Expr\Array_) {
$compiledArray = [];
foreach ($node->items as $arrayItem) {
$compiledValue = $this->__invoke($arrayItem->value);

if (null == $arrayItem->key) {
$compiledArray[] = $compiledValue;
continue;
}

$compiledArray[$this->__invoke($arrayItem->key)] = $compiledValue;
}
return $compiledArray;
return $this->compileArray($node, $context);
}

if ($node instanceof Node\Expr\ConstFetch) {
$firstName = reset($node->name->parts);
switch ($firstName) {
case 'null':
return null;
case 'false':
return false;
case 'true':
return true;
default:
// @todo this should evaluate the VALUE, not the name
/* @see https://github.com/Roave/BetterReflection/issues/19 */
return $firstName;
}
return $this->compileConstFetch($node);
}

if ($node instanceof Node\Expr\ClassConstFetch) {
// @todo this should evaluate the VALUE, not the name
/* @see https://github.com/Roave/BetterReflection/issues/19 */
$className = implode('\\', $node->class->parts);
$constName = $node->name;
return $className . '::' . $constName;
return $this->compileClassConstFetch($node, $context);
}

if ($node instanceof Node\Expr\BinaryOp) {
return $this->compileBinaryOperator($node, $context);
}

throw new Exception\UnableToCompileNode('Unable to compile expression: ' . get_class($node));
}

/**
* Compile arrays
*
* @param Node\Expr\Array_ $arrayNode
* @param CompilerContext $context
* @return array
*/
private function compileArray(Node\Expr\Array_ $arrayNode, CompilerContext $context)
{
$compiledArray = [];
foreach ($arrayNode->items as $arrayItem) {
$compiledValue = $this($arrayItem->value, $context);

if (null == $arrayItem->key) {
$compiledArray[] = $compiledValue;
continue;
}

$compiledArray[$this($arrayItem->key, $context)] = $compiledValue;
}
return $compiledArray;
}

/**
* Compile constant expressions
*
* @param Node\Expr\ConstFetch $constNode
* @return bool|null
*/
private function compileConstFetch(Node\Expr\ConstFetch $constNode)
{
$firstName = reset($constNode->name->parts);
switch ($firstName) {
case 'null':
return null;
case 'false':
return false;
case 'true':
return true;
default:
throw new Exception\UnableToCompileNode('Unable to compile constant expressions');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want to at least try casting the constant expression to string in here, or else there may be rage

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnableToCompileNode::fromConstFetch($constNode) maybe?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously it was just returning a string with the const name, (e.g. FOO would compile to "FOO"), but surely this could cause undesired behaviour?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could indeed cause undesired behavior :-(

}
}

/**
* Compile class constants
*
* @param Node\Expr\ClassConstFetch $node
* @param CompilerContext $context
* @return string
*/
private function compileClassConstFetch(Node\Expr\ClassConstFetch $node, CompilerContext $context)
{
$className = implode('\\', $node->class->parts);

$classInfo = null;
if ('self' === $className || 'static' === $className) {
$classInfo = $context->getSelf();
}

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

/* @var ReflectionClass $classInfo */
$constName = $node->name;
return $classInfo->getConstant($constName);
}

/**
* Compile a binary operator node
*
* @param Node\Expr\BinaryOp $node
* @param CompilerContext $context
* @return mixed
*/
private function compileBinaryOperator(Node\Expr\BinaryOp $node, CompilerContext $context)
{
if ($node instanceof Node\Expr\BinaryOp\Plus) {
return $this($node->left, $context) + $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Mul) {
return $this($node->left, $context) * $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Minus) {
return $this($node->left, $context) - $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Div) {
return $this($node->left, $context) / $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Concat) {
return $this($node->left, $context) . $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\BooleanAnd) {
return $this($node->left, $context) && $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\BooleanOr) {
return $this($node->left, $context) || $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\BitwiseAnd) {
return $this($node->left, $context) & $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\BitwiseOr) {
return $this($node->left, $context) | $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\BitwiseXor) {
return $this($node->left, $context) ^ $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Equal) {
return $this($node->left, $context) == $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Greater) {
return $this($node->left, $context) > $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\GreaterOrEqual) {
return $this($node->left, $context) >= $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Identical) {
return $this($node->left, $context) === $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\LogicalAnd) {
return $this($node->left, $context) and $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\LogicalOr) {
return $this($node->left, $context) or $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\LogicalXor) {
return $this($node->left, $context) xor $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Mod) {
return $this($node->left, $context) % $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\NotEqual) {
return $this($node->left, $context) != $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\NotIdentical) {
return $this($node->left, $context) !== $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Pow) {
return $this($node->left, $context) ** $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\ShiftLeft) {
return $this($node->left, $context) << $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\ShiftRight) {
return $this($node->left, $context) >> $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\Smaller) {
return $this($node->left, $context) < $this($node->right, $context);
}

if ($node instanceof Node\Expr\BinaryOp\SmallerOrEqual) {
return $this($node->left, $context) <= $this($node->right, $context);
}

throw new Exception\UnableToCompileNode('Unable to compile binary operator: ' . get_class($node));
}
}
65 changes: 65 additions & 0 deletions src/NodeCompiler/CompilerContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace BetterReflection\NodeCompiler;

use BetterReflection\Reflection\ReflectionClass;
use BetterReflection\Reflector\Reflector;

class CompilerContext
{
/**
* @var Reflector
*/
private $reflector;

/**
* @var ReflectionClass|null
*/
private $self;

/**
* @param Reflector $reflector
* @param ReflectionClass|null $self
*/
public function __construct(Reflector $reflector, ReflectionClass $self = null)
{
$this->reflector = $reflector;
$this->self = $self;
}

/**
* Does the current context have a "self" or "this"
*
* (e.g. if the context is a function, then no, there will be no self)
*
* @return bool
*/
public function hasSelf()
{
return null !== $this->self;
}

/**
* Get the
*
* @return ReflectionClass|null
*/
public function getSelf()
{
if (!$this->hasSelf()) {
throw new \RuntimeException('The current context does not have a class for self');
}

return $this->self;
}

/**
* Get the reflector
*
* @return Reflector
*/
public function getReflector()
{
return $this->reflector;
}
}
6 changes: 5 additions & 1 deletion src/Reflection/ReflectionClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace BetterReflection\Reflection;

use BetterReflection\NodeCompiler\CompileNodeToValue;
use BetterReflection\NodeCompiler\CompilerContext;
use BetterReflection\Reflection\Exception\NotAClassReflection;
use BetterReflection\Reflection\Exception\NotAnInterfaceReflection;
use BetterReflection\Reflection\Exception\NotAnObject;
Expand Down Expand Up @@ -334,7 +335,10 @@ public function getConstants()
foreach ($this->node->stmts as $stmt) {
if ($stmt instanceof ConstNode) {
$constName = $stmt->consts[0]->name;
$constValue = (new CompileNodeToValue())->__invoke($stmt->consts[0]->value);
$constValue = (new CompileNodeToValue())->__invoke(
$stmt->consts[0]->value,
new CompilerContext($this->reflector, $this)
);
$constants[$constName] = $constValue;
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/Reflection/ReflectionParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace BetterReflection\Reflection;

use BetterReflection\NodeCompiler\CompilerContext;
use BetterReflection\Reflector\ClassReflector;
use BetterReflection\Reflector\Reflector;
use BetterReflection\NodeCompiler\Exception\UnableToCompileNode;
use BetterReflection\TypesFinder\FindParameterType;
use BetterReflection\TypesFinder\FindTypeFromAst;
use phpDocumentor\Reflection\Types;
Expand Down Expand Up @@ -119,8 +121,6 @@ private function parseDefaultValueNode()

$defaultValueNode = $this->node->default;

$this->defaultValue = (new CompileNodeToValue())->__invoke($defaultValueNode);

if ($defaultValueNode instanceof Node\Expr\ClassConstFetch) {
$this->isDefaultValueConstant = true;
$this->defaultValueConstantName = $defaultValueNode->name;
Expand All @@ -132,7 +132,14 @@ private function parseDefaultValueNode()
$this->isDefaultValueConstant = true;
$this->defaultValueConstantName = $defaultValueNode->name->parts[0];
$this->defaultValueConstantType = self::CONST_TYPE_DEFINED;
$this->defaultValue = null;
return;
}

$this->defaultValue = (new CompileNodeToValue())->__invoke(
$defaultValueNode,
new CompilerContext($this->reflector, $this->getDeclaringClass())
);
}

/**
Expand Down
Loading