Multiple improvements

This commit is contained in:
Daniil Gentili 2020-09-01 12:52:44 +02:00
parent 940c2bbab4
commit 440d8b40f2
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
5 changed files with 208 additions and 24 deletions

View File

@ -3,7 +3,18 @@
namespace Phabel;
use PhpParser\Node;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignRef;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
use SplStack;
/**
@ -19,41 +30,94 @@ class Context
*/
public SplStack $parents;
/**
* Whether we're inside an isset expression
* Declared variables stack.
*
* @var SplStack<VariableContext>
*/
public bool $insideIsset = false;
public SplStack $variables;
/**
* Constructor
* Constructor.
*/
public function __construct()
{
$this->parents = new SplStack;
$this->variables = new SplStack;
}
/**
* Push node
* Push node.
*
* @param Node $node Node
*
*
* @return void
*/
public function push(Node $node): void
{
$this->parents->push($node);
if ($node instanceof Isset_) {
$this->insideIsset = true;
if ($node instanceof RootNode) {
$this->variables->push(new VariableContext);
}
if ($node instanceof FunctionLike) {
$variables = \array_fill_keys(
\array_map(
fn (Param $param): string => $param->var->name,
$node->getParams()
),
true
);
if ($node instanceof Closure) {
foreach ($node->uses as $use) {
$variables[$use->var->name] = true;
if ($use->byRef) {
$this->variables->top()->addVar($use->var->name);
}
}
}
if ($node instanceof ArrowFunction) {
$this->variables->top()->addVars($variables);
} else {
$this->variables->push(new VariableContext($variables));
}
} elseif ($node instanceof Assign || $node instanceof AssignOp || $node instanceof AssignRef) {
do {
$node = $node->var;
} while ($node instanceof ArrayDimFetch && $node->var instanceof ArrayDimFetch);
if ($node instanceof Variable && \is_string($node->name)) {
$this->variables->top()->addVar($node->name);
}
} elseif ($node instanceof MethodCall || $node instanceof StaticCall || $node instanceof FuncCall) {
// Cover reference parameters
foreach ($node->args as $argument) {
$argument = $argument->value;
do {
$argument = $argument->var;
} while ($argument instanceof ArrayDimFetch && $argument->var instanceof ArrayDimFetch);
if ($argument instanceof Variable && \is_string($argument->name)) {
$this->variables->top()->addVar($argument->name);
}
}
}
}
/**
* Pop node
* Pop node.
*
* @return void
*/
public function pop(): void
{
if ($this->parents->pop() instanceof Isset_) {
$this->insideIsset = false;
$popped = $this->parents->pop();
if ($popped instanceof RootNode || ($popped instanceof FunctionLike && !$popped instanceof ArrowFunction)) {
$this->variables->pop();
}
}
/**
* Return new unoccupied variable.
*
* @return Variable
*/
public function getVariable(): Variable
{
return new Variable($this->parents->top()->getVar());
}
/**
* Insert nodes before node.
*

View File

@ -2,23 +2,31 @@
namespace Phabel\Plugin;
use Phabel\Context;
use Phabel\Plugin;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Scalar\LNumber;
class NestedExpressionFixer extends Plugin
{
public function enter(Expr $expr): void
public function leave(Expr $expr, Context $context): ?Expr
{
/** @var array<string, array<class-string<Expr>, true>> */
$subNodes = $this->getConfig($class = \get_class($expr), false);
if (!$subNodes) {
return;
return null;
}
foreach ($subNodes as $key => $types) {
/** @var Expr $value */
@ -34,9 +42,19 @@ class NestedExpressionFixer extends Plugin
case Instanceof_::class:
$value = self::callPoly('returnMe', $value);
break;
case ClassConstFetch::class:
// For all the following expressions, wrapping in a ternary breaks return-by-ref
case StaticCall::class:
case StaticPropertyFetch::class:
case FuncCall::class:
$expr->var = self::callPoly('returnMe', $expr->var);
break;
return new Ternary(
new BooleanOr(
new Assign($value = $context->getVariable(), $value),
new LNumber(1)
),
$expr,
new LNumber(0)
);
}
}
}

View File

@ -14,7 +14,7 @@ use PhpParser\Node\Stmt\Foreach_;
class ArrayList extends Plugin
{
/**
* Caled when entering Foreach_ node.
* Called when entering Foreach_ node.
*
* @param Foreach_ $node Node
*

View File

@ -27,7 +27,7 @@ class ListKey extends Plugin
return;
}
[$node->valueVar, $array] = $this->splitList($node->valueVar);
$node->expr = self::callPoly('destructure', $array, $node->expr);
$node->expr = self::callPoly('destructureForeach', $array, $node->expr);
}
/**
* Parse list assignment with custom keys.
@ -56,23 +56,23 @@ class ListKey extends Plugin
return isset($list->items[0]->key);
}
/**
* Split keyed list into new list assignment and array to pass to destructure function.
* Split keyed list into a new list assignment and array to pass to destructure function.
*
* @param List_ $list Keyed list
*
* @return array{0: List_, 1: Array_}
*/
private function splitList(List_ $list): array
private static function splitList(List_ $list): array
{
$newList = [];
$keys = [];
$key = 0; // Technically list assignment does not support mixed keys, but we need this for nested assignments
$key = 0; // Technically a list assignment does not support mixed keys, but we need this for nested assignments
foreach ($list->items as $item) {
if ($item) {
$curKey = $item->key ?? $key++;
$item->key = null;
if ($item->value instanceof List_) {
[$item->value, $keys[$curKey]] = $this->splitList($list);
[$item->value, $keys[$curKey]] = self::splitList($list);
} else {
$keys[$curKey] = null;
}
@ -85,6 +85,22 @@ class ListKey extends Plugin
$keys = BuilderHelpers::normalizeValue($keys);
return [new List_($newList), $keys];
}
/**
* Destructure array of arrays
*
* @param array $keys Custom keys
* @param array $array Array
*
* @psalm-param array<string, null|array> $keys Custom keys
*
* @return \Generator
*/
public static function destructureForeach(array $keys, array $array): \Generator
{
foreach ($array as $value) {
yield self::destructure($keys, $value);
}
}
/**
* Destructure array.
*
@ -98,11 +114,11 @@ class ListKey extends Plugin
public static function destructure(array $keys, array $array): array
{
$res = [];
foreach ($keys as $key => $type) {
if ($type === null) {
foreach ($keys as $key => $subKeys) {
if ($subKeys === null) {
$res[] = $array[$key];
} else {
$res[] = self::destructure($type, $array[$key]);
$res[] = self::destructure($subKeys, $array[$key]);
}
}
return $res;

86
src/VariableContext.php Normal file
View File

@ -0,0 +1,86 @@
<?php
namespace Phabel;
/**
* Represent variables currently in scope.
*/
class VariableContext
{
/**
* Variable list.
*
* @var array<string, true>
*/
private array $variables;
/**
* Custom variable counter.
*/
private int $counter = 0;
/**
* Constructor.
*
* @param array<string, true> $variables Initial variables
*/
public function __construct(array $variables = [])
{
$this->variables = $variables;
}
/**
* Add variable.
*
* @param string $var Variable
*
* @return void
*/
public function addVar(string $var): void
{
$this->variables[$var] = true;
}
/**
* Add variables.
*
* @param array<string, true> $vars Variables
*
* @return void
*/
public function addVars(array $vars): void
{
$this->variables += $vars;
}
/**
* Remove variable.
*
* @param string $var Variable name
*
* @return void
*/
public function removeVar(string $var): void
{
unset($this->variables[$var]);
}
/**
* Check if variable is present.
*
* @param string $var
* @return boolean
*/
public function hasVar(string $var): bool
{
return isset($this->variables[$var]);
}
/**
* Get unused variable name.
*
* @return string
*/
public function getVar(): string
{
do {
$var = 'phabel'.$this->counter;
$this->counter++;
} while (isset($this->variables[$var]));
$this->variables[$var] = true;
return $var;
}
}