Update
This commit is contained in:
parent
47562e4745
commit
d0694ab126
|
@ -31,7 +31,7 @@ class Plugin implements PluginInterface, EventSubscriberInterface
|
|||
{
|
||||
$repoManager = $composer->getRepositoryManager();
|
||||
$repos = $repoManager->getRepositories();
|
||||
$repoManager->prependRepository()
|
||||
$repoManager->prependRepository();
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
|
@ -48,15 +48,14 @@ class Plugin implements PluginInterface, EventSubscriberInterface
|
|||
|
||||
|
||||
/**
|
||||
* Emitted before composer solves dependencies
|
||||
* Emitted before composer solves dependencies.
|
||||
*
|
||||
* @param InstallerEvent $event Event
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onDependencySolve(InstallerEvent $event): void
|
||||
{
|
||||
var_dump($event);
|
||||
\var_dump($event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,10 +30,16 @@ class Context
|
|||
*/
|
||||
public function insertBefore(Node $node, Node ...$nodes): void
|
||||
{
|
||||
if (empty($nodes)) {
|
||||
return;
|
||||
}
|
||||
$subNode = $node->getAttribute('currentNode');
|
||||
$subNodeIndex = $node->getAttribute('currentNodeIndex');
|
||||
\array_splice($node->{$subNode}, $subNodeIndex, 0, $nodes);
|
||||
$node->setAttribute('currentNodeIndex', $subNodeIndex+\count($nodes));
|
||||
$skips = $node->getAttribute('skipNodes', []);
|
||||
$skips []= $subNodeIndex+\count($nodes);
|
||||
$node->setAttribute('skipNodes', $skips);
|
||||
$node->setAttribute('currentNodeIndex', $subNodeIndex - 1);
|
||||
}
|
||||
/**
|
||||
* Insert nodes after node.
|
||||
|
|
|
@ -8,6 +8,7 @@ use PhpParser\Node\Arg;
|
|||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Name;
|
||||
|
@ -146,8 +147,8 @@ abstract class Plugin implements PluginInterface
|
|||
/**
|
||||
* Call function.
|
||||
*
|
||||
* @param class-string|array{0: class-string, 1: string} $name Function name
|
||||
* @param Expr|Arg ...$parameters Parameters
|
||||
* @param class-string|array{0: class-string, 1: string}|callable-string $name Function name
|
||||
* @param Expr|Arg ...$parameters Parameters
|
||||
*
|
||||
* @return FuncCall|StaticCall
|
||||
*/
|
||||
|
@ -168,6 +169,20 @@ abstract class Plugin implements PluginInterface
|
|||
{
|
||||
return self::call([static::class, $name], ...$parameters);
|
||||
}
|
||||
/**
|
||||
* Call method of object.
|
||||
*
|
||||
* @param Expr $name Object name
|
||||
* @param string $method Method
|
||||
* @param Expr|Arg ...$parameters Parameters
|
||||
*
|
||||
* @return MethodCall
|
||||
*/
|
||||
public static function callMethod(Expr $name, string $method, ...$parameters): MethodCall
|
||||
{
|
||||
$parameters = \array_map(fn ($data) => $data instanceof Arg ? $data : new Arg($data), $parameters);
|
||||
return new MethodCall($name, $method, $parameters);
|
||||
}
|
||||
/**
|
||||
* Convert array, int or other literal to node.
|
||||
*
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Spatie\Php7to5\NodeVisitors;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class YieldFromReplacer extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $foreachYield;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function leaveNode(Node $node)
|
||||
{
|
||||
if (!$node instanceof Node\Expr\YieldFrom) {
|
||||
return;
|
||||
}
|
||||
|
||||
$generator = $node->expr;
|
||||
|
||||
return new Node\Expr\Yield_($generator);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Phabel\Plugin;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\NameContext;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class NameResolver extends Plugin
|
||||
{
|
||||
/** @var NameContext Naming context */
|
||||
protected $nameContext;
|
||||
|
||||
/**
|
||||
* Constructs a name resolution visitor.
|
||||
*
|
||||
* Options:
|
||||
* * preserveOriginalNames (default false): An "originalName" attribute will be added to
|
||||
* all name nodes that underwent resolution.
|
||||
* * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a
|
||||
* resolvedName attribute is added. (Names that cannot be statically resolved receive a
|
||||
* namespacedName attribute, as usual.)
|
||||
*
|
||||
* @param ErrorHandler|null $errorHandler Error handler
|
||||
* @param array $options Options
|
||||
*/
|
||||
public function __construct(ErrorHandler $errorHandler = null, array $options = [])
|
||||
{
|
||||
$this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name resolution context.
|
||||
*
|
||||
* @return NameContext
|
||||
*/
|
||||
public function getNameContext(): NameContext
|
||||
{
|
||||
return $this->nameContext;
|
||||
}
|
||||
|
||||
public function beforeTraverse(array $nodes)
|
||||
{
|
||||
$this->nameContext->startNamespace();
|
||||
return null;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Stmt\Namespace_) {
|
||||
$this->nameContext->startNamespace($node->name);
|
||||
} elseif ($node instanceof Stmt\Use_) {
|
||||
foreach ($node->uses as $use) {
|
||||
$this->addAlias($use, $node->type, null);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\GroupUse) {
|
||||
foreach ($node->uses as $use) {
|
||||
$this->addAlias($use, $node->type, $node->prefix);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Class_) {
|
||||
if (null !== $node->extends) {
|
||||
$node->extends = $this->resolveClassName($node->extends);
|
||||
}
|
||||
|
||||
foreach ($node->implements as &$interface) {
|
||||
$interface = $this->resolveClassName($interface);
|
||||
}
|
||||
|
||||
if (null !== $node->name) {
|
||||
$this->addNamespacedName($node);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Interface_) {
|
||||
foreach ($node->extends as &$interface) {
|
||||
$interface = $this->resolveClassName($interface);
|
||||
}
|
||||
|
||||
$this->addNamespacedName($node);
|
||||
} elseif ($node instanceof Stmt\Trait_) {
|
||||
$this->addNamespacedName($node);
|
||||
} elseif ($node instanceof Stmt\Function_) {
|
||||
$this->addNamespacedName($node);
|
||||
$this->resolveSignature($node);
|
||||
} elseif ($node instanceof Stmt\ClassMethod
|
||||
|| $node instanceof Expr\Closure
|
||||
|| $node instanceof Expr\ArrowFunction
|
||||
) {
|
||||
$this->resolveSignature($node);
|
||||
} elseif ($node instanceof Stmt\Property) {
|
||||
if (null !== $node->type) {
|
||||
$node->type = $this->resolveType($node->type);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Const_) {
|
||||
foreach ($node->consts as $const) {
|
||||
$this->addNamespacedName($const);
|
||||
}
|
||||
} elseif ($node instanceof Expr\StaticCall
|
||||
|| $node instanceof Expr\StaticPropertyFetch
|
||||
|| $node instanceof Expr\ClassConstFetch
|
||||
|| $node instanceof Expr\New_
|
||||
|| $node instanceof Expr\Instanceof_
|
||||
) {
|
||||
if ($node->class instanceof Name) {
|
||||
$node->class = $this->resolveClassName($node->class);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Catch_) {
|
||||
foreach ($node->types as &$type) {
|
||||
$type = $this->resolveClassName($type);
|
||||
}
|
||||
} elseif ($node instanceof Expr\FuncCall) {
|
||||
if ($node->name instanceof Name) {
|
||||
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
|
||||
}
|
||||
} elseif ($node instanceof Expr\ConstFetch) {
|
||||
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
|
||||
} elseif ($node instanceof Stmt\TraitUse) {
|
||||
foreach ($node->traits as &$trait) {
|
||||
$trait = $this->resolveClassName($trait);
|
||||
}
|
||||
|
||||
foreach ($node->adaptations as $adaptation) {
|
||||
if (null !== $adaptation->trait) {
|
||||
$adaptation->trait = $this->resolveClassName($adaptation->trait);
|
||||
}
|
||||
|
||||
if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
|
||||
foreach ($adaptation->insteadof as &$insteadof) {
|
||||
$insteadof = $this->resolveClassName($insteadof);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null)
|
||||
{
|
||||
// Add prefix for group uses
|
||||
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
|
||||
// Type is determined either by individual element or whole use declaration
|
||||
$type |= $use->type;
|
||||
|
||||
$this->nameContext->addAlias(
|
||||
$name,
|
||||
(string) $use->getAlias(),
|
||||
$type,
|
||||
$use->getAttributes()
|
||||
);
|
||||
}
|
||||
|
||||
/** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
|
||||
private function resolveSignature($node)
|
||||
{
|
||||
foreach ($node->params as $param) {
|
||||
$param->type = $this->resolveType($param->type);
|
||||
}
|
||||
$node->returnType = $this->resolveType($node->returnType);
|
||||
}
|
||||
|
||||
private function resolveType($node)
|
||||
{
|
||||
if ($node instanceof Name) {
|
||||
return $this->resolveClassName($node);
|
||||
}
|
||||
if ($node instanceof Node\NullableType) {
|
||||
$node->type = $this->resolveType($node->type);
|
||||
return $node;
|
||||
}
|
||||
if ($node instanceof Node\UnionType) {
|
||||
foreach ($node->types as &$type) {
|
||||
$type = $this->resolveType($type);
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve name, according to name resolver options.
|
||||
*
|
||||
* @param Name $name Function or constant name to resolve
|
||||
* @param int $type One of Stmt\Use_::TYPE_*
|
||||
*
|
||||
* @return Name Resolved name, or original name with attribute
|
||||
*/
|
||||
protected function resolveName(Name $name, int $type): Name
|
||||
{
|
||||
if (!$this->replaceNodes) {
|
||||
$resolvedName = $this->nameContext->getResolvedName($name, $type);
|
||||
if (null !== $resolvedName) {
|
||||
$name->setAttribute('resolvedName', $resolvedName);
|
||||
} else {
|
||||
$name->setAttribute('namespacedName', FullyQualified::concat(
|
||||
$this->nameContext->getNamespace(),
|
||||
$name,
|
||||
$name->getAttributes()
|
||||
));
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
if ($this->preserveOriginalNames) {
|
||||
// Save the original name
|
||||
$originalName = $name;
|
||||
$name = clone $originalName;
|
||||
$name->setAttribute('originalName', $originalName);
|
||||
}
|
||||
|
||||
$resolvedName = $this->nameContext->getResolvedName($name, $type);
|
||||
if (null !== $resolvedName) {
|
||||
return $resolvedName;
|
||||
}
|
||||
|
||||
// unqualified names inside a namespace cannot be resolved at compile-time
|
||||
// add the namespaced version of the name as an attribute
|
||||
$name->setAttribute('namespacedName', FullyQualified::concat(
|
||||
$this->nameContext->getNamespace(),
|
||||
$name,
|
||||
$name->getAttributes()
|
||||
));
|
||||
return $name;
|
||||
}
|
||||
|
||||
protected function resolveClassName(Name $name)
|
||||
{
|
||||
return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
|
||||
}
|
||||
|
||||
protected function addNamespacedName(Node $node)
|
||||
{
|
||||
$node->namespacedName = Name::concat(
|
||||
$this->nameContext->getNamespace(),
|
||||
(string) $node->name
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Phabel\Plugin;
|
||||
|
||||
use Phabel\Context;
|
||||
use Phabel\Plugin;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use SplQueue;
|
||||
|
||||
/**
|
||||
* Optimizes concatenation of multiple strings.
|
||||
*/
|
||||
class StringConcatOptimizer extends Plugin
|
||||
{
|
||||
private function enqueue(Concat $concat, SplQueue $queue)
|
||||
{
|
||||
if ($concat->left instanceof Concat) {
|
||||
$this->enqueue($concat->left, $queue);
|
||||
} else {
|
||||
$queue->enqueue($concat->left);
|
||||
}
|
||||
if ($concat->right instanceof Concat) {
|
||||
$this->enqueue($concat->right, $queue);
|
||||
} else {
|
||||
$queue->enqueue($concat->right);
|
||||
}
|
||||
}
|
||||
public function enter(Concat $concat, Context $ctx): ?Node
|
||||
{
|
||||
if ($ctx->parents->top() instanceof Concat) {
|
||||
return;
|
||||
}
|
||||
$concatQueue = new SplQueue;
|
||||
$this->enqueue($concat, $concatQueue);
|
||||
$newQueue = new SplQueue;
|
||||
$prevNode = $concatQueue->dequeue();
|
||||
while ($node = $concatQueue->dequeue()) {
|
||||
if ($node instanceof String_ && $prevNode instanceof String_) {
|
||||
$prevNode = new String_($prevNode->value.$node->value);
|
||||
} else {
|
||||
$newQueue->enqueue($prevNode);
|
||||
$prevNode = $node;
|
||||
}
|
||||
}
|
||||
$newQueue->enqueue($prevNode);
|
||||
|
||||
if ($newQueue->count() === 1) {
|
||||
return $newQueue->dequeue();
|
||||
}
|
||||
$concat = new Concat($newQueue->dequeue(), $newQueue->dequeue());
|
||||
while ($node = $newQueue->dequeue()) {
|
||||
$concat = new Concat($concat, $newQueue->dequeue());
|
||||
}
|
||||
return $concat;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,10 @@ namespace Phabel\Plugin;
|
|||
|
||||
use Phabel\Context;
|
||||
use Phabel\Plugin;
|
||||
use Phabel\Target\Php74\ArrowClosure;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\ArrowFunction;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\AssignRef;
|
||||
|
@ -27,6 +30,7 @@ use PhpParser\Node\Scalar\LNumber;
|
|||
use PhpParser\Node\Scalar\MagicConst\Function_ as MagicConstFunction_;
|
||||
use PhpParser\Node\Scalar\MagicConst\Method;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Foreach_;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
|
@ -42,27 +46,53 @@ use SplStack;
|
|||
*/
|
||||
class TypeHintStripper extends Plugin
|
||||
{
|
||||
private const IGNORE_RETURN = 0;
|
||||
private const VOID_RETURN = 1;
|
||||
private const TYPE_RETURN = 2;
|
||||
/**
|
||||
* Stack.
|
||||
*
|
||||
* @template T as array{0: self::IGNORE_RETURN|self::VOID_RETURN}|array{0: self::TYPE_RETURN, 1: Node, 2: bool, 3: bool, 4: Node, 5: BooleanNot}
|
||||
*
|
||||
* @var SplStack<T>
|
||||
*/
|
||||
private SplStack $stack;
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->stack = $stack;
|
||||
$this->stack = new SplStack;
|
||||
}
|
||||
/**
|
||||
* Convert a function to a closure.
|
||||
*/
|
||||
private function toClosure(FunctionLike &$func): void
|
||||
{
|
||||
if ($func instanceof ArrowFunction) {
|
||||
$nodes = [];
|
||||
foreach ($func->getSubNodeNames() as $node) {
|
||||
$nodes[$node] = $func->{$nodes};
|
||||
}
|
||||
$func = new Closure($nodes, $func->getAttributes());
|
||||
$func = ArrowClosure::enterClosure($func);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generate.
|
||||
*
|
||||
* @param Variable $var Variable to check
|
||||
* @param (Name|Identifier)[] $types Types to check
|
||||
* @param boolean $fromNullable Whether this type is nullable
|
||||
*
|
||||
* @return array{0: bool, 1: Node, 2: BooleanNot} Whether the polyfilled gettype should be used, the error message, the condition
|
||||
*/
|
||||
private function generateConditions(Variable $var, array $types, bool $fromNullable = false): array
|
||||
{
|
||||
/** @var bool Whether no explicit classes were referenced */
|
||||
$noOopTypes = true;
|
||||
/** @var string[] */
|
||||
$typeNames = [];
|
||||
/** @var Expr[] */
|
||||
$conditions = [];
|
||||
/** @var string Last string type name */
|
||||
$stringType = '';
|
||||
foreach ($types as &$type) {
|
||||
foreach ($types as $type) {
|
||||
$typeNames []= $type->toString();
|
||||
|
||||
if ($type instanceof Identifier) {
|
||||
|
@ -80,11 +110,11 @@ class TypeHintStripper extends Plugin
|
|||
$stringType = new String_($typeName === 'callable' ?
|
||||
$typeName :
|
||||
($typeName === 'object' ? 'an object' : "of type $typeName"));
|
||||
$type = Plugin::call("is_$typeName", $var);
|
||||
$conditions []= Plugin::call("is_$typeName", $var);
|
||||
break;
|
||||
case 'iterable':
|
||||
$stringType = new String_('iterable');
|
||||
$type = new BooleanOr(
|
||||
$conditions []= new BooleanOr(
|
||||
Plugin::call("is_array", $var),
|
||||
new Instanceof_($var, new Name(\Traversable::class))
|
||||
);
|
||||
|
@ -92,24 +122,29 @@ class TypeHintStripper extends Plugin
|
|||
default:
|
||||
$noOopTypes = false;
|
||||
$stringType = $type->isSpecialClassName() ?
|
||||
new Concat(new String_("an instance of "), new ClassConstFetch($type, new Identifier('class'))) :
|
||||
new Concat(new String_("an instance of "), new ClassConstFetch(new Name($typeName), new Identifier('class'))) :
|
||||
new String_("an instance of ".$type->toString());
|
||||
$type = new Instanceof_($var, $type);
|
||||
$conditions []= new Instanceof_($var, new Name($typeName));
|
||||
}
|
||||
} else {
|
||||
$noOopTypes = false;
|
||||
$stringType = new String_("an instance of ".$type->toString());
|
||||
if ($type->toString() === 'Stringable' || $type->toString() === \Stringable::class) {
|
||||
$type = Plugin::callPoly("is_stringable", $var);
|
||||
} else {
|
||||
$type = new Instanceof_($var, $type);
|
||||
}
|
||||
$conditions []= new Instanceof_($var, $type);
|
||||
}
|
||||
}
|
||||
if (\count($typeNames) > 1) {
|
||||
$stringType = new String_(\implode("|", $typeNames));
|
||||
}
|
||||
$condition = new BooleanNot(\count($types) === 1 ? $types[0] : \array_reduce($types, fn (Node $a, Node $b): BooleanOr => new BooleanOr($a, $b)));
|
||||
if ($fromNullable) {
|
||||
$stringType = new Concat($stringType, new String_(' or null'));
|
||||
$conditions []= Plugin::call("is_null", $var);
|
||||
}
|
||||
$initial = \array_shift($conditions);
|
||||
$condition = new BooleanNot(
|
||||
empty($conditions)
|
||||
? $initial
|
||||
: \array_reduce($conditions, fn (Expr $a, Expr $b): BooleanOr => new BooleanOr($a, $b), $initial)
|
||||
);
|
||||
return [$noOopTypes, $stringType, $condition];
|
||||
}
|
||||
/**
|
||||
|
@ -118,12 +153,12 @@ class TypeHintStripper extends Plugin
|
|||
* @param Variable $var Variable
|
||||
* @param null|Identifier|Name|NullableType|UnionType $type Type
|
||||
*
|
||||
* @return array{0: bool, 1: Node, 2: BooleanNot} Conditions for if
|
||||
* @return null|array{0: bool, 1: Node, 2: BooleanNot} Whether the polyfilled gettype should be used, the error message, the condition
|
||||
*/
|
||||
private function strip(Variable $var, ?Node $type): array
|
||||
private function strip(Variable $var, ?Node $type): ?array
|
||||
{
|
||||
if (!$type) {
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
if ($type instanceof UnionType && $this->getConfig('union', false)) {
|
||||
return $this->generateConditions($var, $type->types);
|
||||
|
@ -142,9 +177,9 @@ class TypeHintStripper extends Plugin
|
|||
*
|
||||
* @param FunctionLike $func Function
|
||||
*
|
||||
* @return void
|
||||
* @return ?FunctionLike
|
||||
*/
|
||||
public function enterFunction(FunctionLike $func, Context $ctx): void
|
||||
public function enterFunction(FunctionLike $func, Context $ctx): ?FunctionLike
|
||||
{
|
||||
$functionName = new Method();
|
||||
if ($func instanceof ClassMethod) {
|
||||
|
@ -169,9 +204,9 @@ class TypeHintStripper extends Plugin
|
|||
$start = new Concat($start, new String_(", "));
|
||||
$start = new Concat($start, $noOop ? self::call('gettype', $param->var) : self::callPoly('gettype', $param->var));
|
||||
$start = new Concat($start, new String_(" given, called in "));
|
||||
$start = new Concat($start, self::callPoly('trace', 0));
|
||||
$start = new Concat($start, self::callPoly('trace', new LNumber(0)));
|
||||
|
||||
$if = new If_($condition, [new Throw_(new New_(new FullyQualified(\TypeError::class), [$start]))]);
|
||||
$if = new If_($condition, [new Throw_(new New_(new FullyQualified(\TypeError::class), [new Arg($start)]))]);
|
||||
if ($param->variadic) {
|
||||
$stmts []= new Foreach_($param->var, new Variable('phabelVariadic'), ['keyVar' => new Variable('phabelVariadicIndex'), 'stmts' => [$if]]);
|
||||
} else {
|
||||
|
@ -180,34 +215,34 @@ class TypeHintStripper extends Plugin
|
|||
}
|
||||
if ($stmts) {
|
||||
$this->toClosure($func);
|
||||
$func->stmts = \array_merge($stmts, $func->getStmts());
|
||||
$func->stmts = \array_merge($stmts, $func->getStmts() ?? []);
|
||||
}
|
||||
|
||||
if ($this->getConfig('void', false) && $func->getReturnType() instanceof Identifier && $func->getReturnType()->toLowerString() === 'void') {
|
||||
$this->toClosure($func);
|
||||
$this->stack->push([true]);
|
||||
$this->stack->push([self::VOID_RETURN]);
|
||||
}
|
||||
$var = new Variable('phabelReturn');
|
||||
if (!$condition = $this->strip($var, $func->getReturnType(), false)) {
|
||||
$this->stack->push([null]);
|
||||
return;
|
||||
if (!$condition = $this->strip($var, $func->getReturnType())) {
|
||||
$this->stack->push([self::IGNORE_RETURN]);
|
||||
return null;
|
||||
}
|
||||
$this->toClosure($func);
|
||||
$this->stack->push([false, $functionName, $func->returnsByRef(), ...$condition]);
|
||||
$this->stack->push([self::TYPE_RETURN, $functionName, $func->returnsByRef(), ...$condition]);
|
||||
return $func;
|
||||
}
|
||||
public function enterReturn(Return_ $return, Context $ctx): ?Node
|
||||
{
|
||||
$current = $this->stack->top();
|
||||
if ($current[0] === null) {
|
||||
return;
|
||||
if ($current[0] === self::IGNORE_RETURN) {
|
||||
return null;
|
||||
}
|
||||
if ($current[0] === true) {
|
||||
if ($current[0] === self::VOID_RETURN) {
|
||||
if ($return->expr !== null) {
|
||||
// This should be a transpilation error, wait for better stack traces before throwing here
|
||||
return new Throw_(new New_(new FullyQualified(\ParseError::class), [new String_("A void function must not return a value")]));
|
||||
}
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
[, $functionName, $byRef, $noOop, $string, $condition] = $current;
|
||||
|
||||
|
@ -223,17 +258,31 @@ class TypeHintStripper extends Plugin
|
|||
$start = new Concat($start, new String_(" returned in "));
|
||||
$start = new Concat($start, self::callPoly('trace', 0));
|
||||
|
||||
$if = new If_($condition, [new Throw_(new New_(new FullyQualified(\TypeError::class), [$start]))]);
|
||||
$if = new If_($condition, [new Throw_(new New_(new FullyQualified(\TypeError::class), [new Arg($start)]))]);
|
||||
|
||||
$return->expr = $var;
|
||||
|
||||
$ctx->insertBefore($ctx->parents->top(), $assign, $if);
|
||||
}
|
||||
public static function trace($index): string
|
||||
/**
|
||||
* Get trace string for errors.
|
||||
*
|
||||
* @param int $index Index
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function trace($index)
|
||||
{
|
||||
$trace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[$index];
|
||||
return ($trace['file'] ?? '').' on line '.($trace['line'] ?? '');
|
||||
}
|
||||
/**
|
||||
* Get type string or object.
|
||||
*
|
||||
* @param mixed $object Object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function gettype($object)
|
||||
{
|
||||
if (\is_object($object)) {
|
||||
|
@ -243,10 +292,11 @@ class TypeHintStripper extends Plugin
|
|||
return \gettype($object);
|
||||
}
|
||||
|
||||
public static function is_stringable($string): bool
|
||||
{
|
||||
return is_string($string) || (is_object($string) && method_exists($string, '__toString'));
|
||||
}
|
||||
/**
|
||||
* Runwithafter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function runWithAfter(): array
|
||||
{
|
||||
return [StringConcatOptimizer::class];
|
||||
|
|
|
@ -6,12 +6,12 @@ use PhpParser\Node;
|
|||
use PhpParser\NodeAbstract;
|
||||
|
||||
/**
|
||||
* Root node
|
||||
* Root node.
|
||||
*/
|
||||
class RootNode extends NodeAbstract
|
||||
{
|
||||
/**
|
||||
* Children
|
||||
* Children.
|
||||
*
|
||||
* @var Node[]
|
||||
*/
|
||||
|
@ -29,4 +29,4 @@ class RootNode extends NodeAbstract
|
|||
{
|
||||
return 'rootNode';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Phabel\Target\Php70;
|
|||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\Plugin\TypeHintStripper;
|
||||
use Phabel\Target\Php71\MultipleCatchReplacer;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
|
||||
use PhpParser\Node\Expr\Instanceof_;
|
||||
|
@ -25,7 +26,6 @@ class ThrowableReplacer extends Plugin
|
|||
*/
|
||||
private function isThrowable(string $type): bool
|
||||
{
|
||||
// Make this less ugly when we implement a namespace context
|
||||
return $type === \Throwable::class || $type === 'Throwable';
|
||||
}
|
||||
/**
|
||||
|
@ -86,7 +86,8 @@ class ThrowableReplacer extends Plugin
|
|||
\Throwable::class,
|
||||
'Throwable'
|
||||
]
|
||||
]
|
||||
],
|
||||
MultipleCatchReplacer::class => []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Phabel\Target\Php70;
|
||||
|
||||
use Phabel\Context;
|
||||
use Phabel\Plugin;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\Instanceof_;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Expr\YieldFrom;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PhpParser\Node\Stmt\While_;
|
||||
|
||||
class YieldFromReplacer extends Plugin
|
||||
{
|
||||
private string $phabelVar = '';
|
||||
private int $phabelCount = 0;
|
||||
public function shouldRunFile(string $file): bool
|
||||
{
|
||||
$this->phabelVar = 'phabelGeneratorYieldFrom'.\hash('sha256', $file);
|
||||
return parent::shouldRunFile($file);
|
||||
}
|
||||
public function enterNode(YieldFrom $node, Context $ctx)
|
||||
{
|
||||
$var = new Variable($this->phabelVar.($this->phabelCount++));
|
||||
$assign = new Assign($var, $node->expr);
|
||||
$ifInstanceof = new If_(new Instanceof_(Plugin::callMethod($var, 'valid'), new FullyQualified(\YieldReturnValue::class)), ['stmts' => [new Assign()]]);
|
||||
$while = new While_(new MethodCall($var, new Identifier('valid')));
|
||||
foreach ($ctx->parents as $node) {
|
||||
if ($node->hasAttribute('currentNodeIndex')) {
|
||||
$ctx->insertBefore($node, $node->expr);
|
||||
}
|
||||
}
|
||||
$generator = $node->expr;
|
||||
|
||||
return new Node\Expr\Yield_($generator);
|
||||
}
|
||||
}
|
|
@ -35,15 +35,4 @@ class MultipleCatchReplacer extends Plugin
|
|||
}
|
||||
$node->catches = $catches;
|
||||
}
|
||||
/**
|
||||
* Extends throwable replacer.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @psalm-return class-string
|
||||
*/
|
||||
public static function runWithBefore(): string
|
||||
{
|
||||
return [ThrowableReplacer::class];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace Spatie\Php7to5\NodeVisitors;
|
|||
use Phabel\Plugin;
|
||||
use Phabel\Plugin\TypeHintStripper;
|
||||
|
||||
class NullableTypeRemover extends Plugin
|
||||
class NullableType extends Plugin
|
||||
{
|
||||
/**
|
||||
* Remove nullable typehint.
|
||||
|
@ -16,7 +16,7 @@ class NullableTypeRemover extends Plugin
|
|||
{
|
||||
return [
|
||||
TypeHintStripper::class => [
|
||||
'nulable' => true
|
||||
'nullable' => true
|
||||
]
|
||||
];
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Spatie\Php7to5\NodeVisitors;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\Plugin\TypeHintStripper;
|
||||
|
||||
class VoidReturnType extends Plugin
|
||||
{
|
||||
/**
|
||||
* Remove void return typehint.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function runAfter(): array
|
||||
{
|
||||
return [
|
||||
TypeHintStripper::class => [
|
||||
'void' => true
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Phabel\Target\Php74;
|
||||
|
||||
use PhpParser\Node\Expr\ArrowFunction;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
|
||||
/**
|
||||
* Turn an arrow function into a closure.
|
||||
*/
|
||||
class ArrowClosure
|
||||
{
|
||||
/**
|
||||
* Enter arrow function.
|
||||
*
|
||||
* @param ArrowFunction $func Arrow function
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public static function enterClosure(ArrowFunction $func): Closure
|
||||
{
|
||||
$nodes = [];
|
||||
foreach ($func->getSubNodeNames() as $node) {
|
||||
$nodes[$node] = $func->{$node};
|
||||
}
|
||||
return new Closure($nodes, $func->getAttributes());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Phabel\Target;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\UnionTypeStripper;
|
||||
|
||||
/**
|
||||
* Makes changes necessary to polyfill PHP 8.0 and run on PHP 7.4 and below.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
class Php80 extends Plugin
|
||||
{
|
||||
public static function composerRequires(): array
|
||||
{
|
||||
return ['symfony/polyfill-php80' => '*'];
|
||||
}
|
||||
|
||||
public static function runWithAfter(): array
|
||||
{
|
||||
return [
|
||||
UnionTypeStripper::class,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Phabel;
|
||||
|
||||
use Phabel\Plugin\TypeHintStripper;
|
||||
|
||||
/**
|
||||
* Strip union types, polyfilling type checks.
|
||||
*/
|
||||
class UnionTypeStripper extends Plugin
|
||||
{
|
||||
/**
|
||||
* Run with before.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function runWithAfter(): array
|
||||
{
|
||||
return [
|
||||
TypeHintStripper::class => [
|
||||
'union' => true
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
|
@ -96,6 +96,7 @@ class Traverser
|
|||
}
|
||||
$ast = new RootNode($this->parser->parse(\file_get_contents($file)));
|
||||
$context = new Context;
|
||||
$context->parents->push($ast);
|
||||
foreach ($reducedQueue as $queue) {
|
||||
$this->traverseNode($ast, $queue, $context);
|
||||
}
|
||||
|
@ -106,7 +107,7 @@ class Traverser
|
|||
* @param Node &$node Node
|
||||
* @param SplQueue<Plugin> $plugins Plugins
|
||||
* @param Context $context Context
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function traverseNode(Node &$node, SplQueue $plugins, Context $context): void
|
||||
|
@ -134,10 +135,15 @@ class Traverser
|
|||
|
||||
$subNode = &$node->{$name};
|
||||
if (\is_array($subNode)) {
|
||||
foreach ($subNode as $index => &$subNodeNode) {
|
||||
for ($index = 0; $index < \count($subNode);) {
|
||||
$node->setAttribute('currentNodeIndex', $index);
|
||||
$this->traverseNode($subNodeNode, $plugins, $context);
|
||||
$index = $node->getAttribute('currentNodeIndex');
|
||||
do {
|
||||
$index++;
|
||||
} while (\in_array($index, $node->getAttribute('skipNodes', [])));
|
||||
}
|
||||
$node->setAttribute('skipNodes', []);
|
||||
} else {
|
||||
$this->traverseNode($subNode, $plugins, $context);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue