Multiple improvements
This commit is contained in:
parent
940c2bbab4
commit
440d8b40f2
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue