Add expression generator

This commit is contained in:
Daniil Gentili 2020-08-30 16:58:59 +02:00
parent 5228af041e
commit fb9dac3d95
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
19 changed files with 594 additions and 165 deletions

View File

@ -9,7 +9,8 @@
"require-dev": {
"phpunit/phpunit": "^7 | ^8 | ^9",
"amphp/php-cs-fixer-config": "dev-master",
"composer/composer": "^1|^2"
"composer/composer": "^1|^2",
"haydenpierce/class-finder": "^0.4.2"
},
"license": "MIT",
"authors": [{

View File

@ -1,32 +0,0 @@
<?php
namespace Spatie\Php7to5\NodeVisitors;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
class YieldReturnDetector extends NodeVisitorAbstract
{
protected $hasYield = [];
/**
* {@inheritdoc}
*/
public function enterNode(Node $node)
{
if ($node instanceof Node\FunctionLike) {
$this->hasYield []= $node;
}
if ($node instanceof Node\Expr\Yield_ ||
$node instanceof Node\Expr\YieldFrom
) {
\end($this->hasYield)->hasYield = true;
}
}
public function leaveNode(Node $node)
{
if ($node instanceof Node\FunctionLike) {
\array_pop($this->hasYield);
}
}
}

View File

@ -1,55 +0,0 @@
<?php
namespace Spatie\Php7to5\NodeVisitors;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
class YieldReturnReplacer extends NodeVisitorAbstract
{
protected $functions = [];
/**
* {@inheritdoc}
*/
public function enterNode(Node $node)
{
if ($node instanceof Node\FunctionLike) {
$this->functions[] = $node;
}
}
/**
* {@inheritdoc}
*/
public function leaveNode(Node $node)
{
if ($node instanceof Node\FunctionLike) {
\array_pop($this->functions);
return;
}
if (!$node instanceof Node\Stmt\Return_) {
return;
}
if ($node->expr === null) {
return new Node\Stmt\Return_();
}
if (!(\end($this->functions)->hasYield ?? false)) {
return;
}
$value = $node->expr;
$newReturn = new Node\Expr\Yield_(
new Node\Expr\New_(
new Node\Expr\ConstFetch(
new Node\Name('\YieldReturnValue')
),
[$value]
)
);
$stmts = [$newReturn, new Node\Stmt\Return_()];
return $stmts;
}
}

View File

@ -3,17 +3,27 @@
namespace Phabel\Plugin;
use Phabel\Plugin;
use Phabel\Traverser;
use PhpParser\Builder\FunctionLike;
class ReGenerator extends Plugin
{
const SHOULD_ATTRIBUTE = 'shouldRegenerate';
/**
* Custom traverser.
*/
private Traverser $traverser;
public function __construct()
{
$this->traverser = Traverser::fromPlugin(new ReGeneratorInternal);
}
public function enter(FunctionLike $function)
{
if (!$function->getAttribute(self::SHOULD_ATTRIBUTE, false)) {
return;
}
$this->traverser->traverseAst($function);
}
public function runAfter(): array
{

View File

@ -12,62 +12,55 @@ class ReGenerator implements \Iterator
*
* @var array
*/
private $variables = [];
public $variables = [];
/**
* Return value.
*
* @var mixed
*/
private $returnValue;
public $returnValue;
/**
* Yield key.
*
* @var mixed
*/
private $yieldKey;
public $yieldKey;
/**
* Yield value.
*
* @var mixed
*/
private $yieldValue;
public $yieldValue;
/**
* Value sent from the outside.
*
* @var mixed
*/
private $sentValue;
public $sentValue;
/**
* Exception sent from the outside.
*/
private ?\Throwable $sentException = null;
public ?\Throwable $sentException = null;
/**
* Current state of state machine.
*/
private int $state = 0;
public int $state = 0;
/**
* Whether the generator has returned.
*/
private bool $returned = false;
public bool $returned = false;
/**
* Whether the generator was started.
*/
private bool $started = false;
public bool $started = false;
/**
* Actual generator function.
*/
private \Closure $generator;
/**
* Yielded from (re)generator.
*
* @var \Generator|self|null
*/
private $yieldedFrom;
public \Closure $generator;
/**
* Construct regenerator.
@ -97,35 +90,26 @@ class ReGenerator implements \Iterator
private function start(): void
{
if (!$this->started) {
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned, $this->yieldedFrom);
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned);
$this->started = true;
}
}
/**
* Send value into generator
*
* @param mixed $value Value
*
* @return mixed
*/
public function send($value)
{
$this->start();
if ($this->yieldedFrom) {
try {
$result = $this->yieldedFrom->send($value);
} catch (\Throwable $exception) {
$e = $exception;
}
if (!$this->yieldedFrom->valid()) { // Returned from yield from
$returnValue = \method_exists($this->yieldedFrom, 'getReturn') ? $this->yieldedFrom->getReturn() : null;
$this->yieldedFrom = null;
if (isset($e)) {
return $this->throw($e);
}
return $this->send($returnValue);
}
return $result;
}
$value = $this->yieldValue;
if (!$this->returned) {
$this->sentValue = $value;
try {
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned, $this->yieldedFrom);
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned);
} catch (\Throwable $e) {
$this->returned = true;
throw $e;
@ -135,30 +119,21 @@ class ReGenerator implements \Iterator
}
return $value;
}
/**
* Throw value into generator
*
* @param \Throwable $throwable Excpeption
*
* @return mixed
*/
public function throw(\Throwable $throwable)
{
$this->start();
if ($this->yieldedFrom) {
try {
$result = $this->yieldedFrom->throw($throwable);
} catch (\Throwable $exception) {
$e = $exception;
}
if (!$this->yieldedFrom->valid()) { // Returned from yield from
$returnValue = \method_exists($this->yieldedFrom, 'getReturn') ? $this->yieldedFrom->getReturn() : null;
$this->yieldedFrom = null;
if (isset($e)) {
return $this->throw($e);
}
return $this->send($returnValue);
}
return $result;
}
$value = $this->yieldValue;
if (!$this->returned) {
$this->sentException = $value;
try {
($this->generator)($this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned, $this->yieldedFrom);
($this->generator)($this->state, $this->variables, $this->yieldKey, $this->yieldValue, $this->sentValue, $this->sentException, $this->returnValue, $this->returned);
} catch (\Throwable $e) {
$this->returned = true;
throw $e;
@ -169,31 +144,46 @@ class ReGenerator implements \Iterator
return $value;
}
/**
* Get current value
*
* @return mixed
*/
public function current()
{
if ($this->yieldedFrom) {
return $this->yieldedFrom->current();
}
$this->start();
return $this->yieldValue;
}
/**
* Get current key
*
* @return mixed
*/
public function key()
{
if ($this->yieldedFrom) {
return $this->yieldedFrom->key();
}
$this->start();
return $this->yieldKey;
}
/**
* Advance generator
*
* @return void
*/
public function next(): void
{
$this->send(null);
}
/**
* Rewind generator
*
* @return void
*/
public function rewind(): void
{
if ($this->started && !$this->returned) {
throw new \Exception('Cannot rewind a generator that was already run');
}
$this->state = 0;
$this->started = false;
$this->returned = false;
$this->returnValue = null;
@ -201,10 +191,14 @@ class ReGenerator implements \Iterator
$this->yieldValue = null;
$this->sentValue = null;
$this->sentException = null;
$this->yieldedFrom = null;
$this->variables = [];
$this->start();
}
/**
* Check if generator is valid
*
* @return boolean
*/
public function valid(): bool
{
return !$this->returned;

View File

@ -0,0 +1,46 @@
<?php
namespace Phabel\Plugin;
use Phabel\Plugin;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use SplQueue;
/**
* Internal regenerator traversor.
*/
class ReGeneratorInternal extends Plugin
{
/**
* List of nodes for each case.
*
* @var SplQueue<SplQueue<Node>>
*/
private SplQueue $states;
public function __construct()
{
$this->states = new SplQueue;
$this->states->enqueue(new SplQueue);
}
/**
* Push node to current case.
*
* @param Node $node Node
*
* @return void
*/
private function pushNode(Node $node): void
{
$this->states->top()->enqueue($node);
}
private function pushState(): int
{
$this->states->enqueue(new SplQueue);
return $this->states->count()-1;
}
public function enterRoot(FunctionLike $func)
{
}
}

View File

@ -57,12 +57,14 @@ class TypeHintStripper extends Plugin
* @var SplStack<T>
*/
private SplStack $stack;
private ArrowClosure $converter;
/**
* Constructor.
*/
public function __construct()
{
$this->stack = new SplStack;
$this->converter = new ArrowClosure;
}
/**
* Convert a function to a closure.
@ -70,7 +72,7 @@ class TypeHintStripper extends Plugin
private function toClosure(FunctionLike &$func): void
{
if ($func instanceof ArrowFunction) {
$func = ArrowClosure::enterClosure($func);
$func = $this->converter->enterClosure($func);
}
}
/**

View File

@ -4,10 +4,13 @@ namespace Phabel\Target\Php70;
use Phabel\Plugin;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Expr\Yield_;
/**
@ -25,13 +28,11 @@ class CompoundAccess extends Plugin
public function enterIsset(Isset_ $node): void
{
foreach ($node->vars as &$var) {
if (!$var instanceof ArrayDimFetch) {
continue;
if ($var instanceof ArrayDimFetch
&& $var->var instanceof Expr
&& !($var->var instanceof Variable || $var->var instanceof FuncCall) {
$var->var = self::callPoly('returnMe', $var->var);
}
if (!$var->var instanceof ClassConstFetch) {
continue;
}
$var->var = self::callPoly('returnMe', $var->var);
}
}
/**

View File

@ -3,6 +3,7 @@
namespace Phabel\Target\Php71;
use Phabel\Plugin;
use PhpParser\BuilderHelpers;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\List_;
@ -81,7 +82,7 @@ class ListKey extends Plugin
}
}
/** @var Array_ */
$keys = self::toLiteral($keys);
$keys = BuilderHelpers::normalizeValue($keys);
return [new List_($newList), $keys];
}
/**

View File

@ -3,6 +3,7 @@
namespace Phabel\Target;
use Phabel\Plugin;
use Phabel\Target\Php72\ObjectTypeHintReplacer;
/**
* Makes changes necessary to polyfill PHP 7.2 and run on PHP 7.1 and below.
@ -16,4 +17,11 @@ class Php72 extends Plugin
{
return ['symfony/polyfill-php72' => '*'];
}
public static function runWithAfter(): array
{
return [
ObjectTypeHintReplacer::class
];
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Phabel\Target\Php72;
use Phabel\Plugin;
use Phabel\Plugin\TypeHintStripper;
class ObjectTypeHintReplacer extends Plugin
{
/**
* Alias.
*
* @return array
*/
public static function runAfter(): array
{
return [
TypeHintStripper::class => [
'types' => ['object']
]
];
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Phabel\Target\Php74;
use Phabel\Plugin;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\FuncCall;
class ArrayUnpack extends Plugin
{
public function enter(Array_ $array): ?FuncCall
{
$hasUnpack = false;
foreach ($array->items as $item) {
if ($item->unpack) {
$hasUnpack = true;
break;
}
}
if (!$hasUnpack) {
return;
}
$args = [];
$array = new Array_();
foreach ($array->items as $item) {
if ($item->unpack) {
if ($array->items) {
$args []= new Arg($array);
$array = new Array_();
}
$args []= new Arg($item->value);
} else {
$array->items []= $item;
}
}
if ($array->items) {
$args []= new Arg($array);
}
return Plugin::call("array_merge", ...$args);
}
}

View File

@ -2,14 +2,47 @@
namespace Phabel\Target\Php74;
use Phabel\Plugin;
use Phabel\Traverser;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\ClosureUse;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
/**
* Turn an arrow function into a closure.
*/
class ArrowClosure
{
/**
* Traverser.
*/
private Traverser $traverser;
/**
* Finder plugin.
*/
private Plugin $finderPlugin;
/**
* Constructor.
*/
public function __construct()
{
$this->finderPlugin = new class extends Plugin {
private array $found = [];
public function enter(Variable $var)
{
$this->found[$var->name]= new ClosureUse($var, true);
}
public function getFound(): array
{
$found = $this->found;
$this->found = [];
return $found;
}
};
$this->traverser = Traverser::fromPlugin($this->finderPlugin);
}
/**
* Enter arrow function.
*
@ -17,12 +50,24 @@ class ArrowClosure
*
* @return Closure
*/
public static function enterClosure(ArrowFunction $func): Closure
public function enterClosure(ArrowFunction $func): Closure
{
$nodes = [];
foreach ($func->getSubNodeNames() as $node) {
$nodes[$node] = $func->{$node};
}
$params = [];
foreach ($nodes['params'] ?? [] as $param) {
$params[$param->var->name] = true;
}
$this->traverser->traverseAst($func);
$nodes['uses'] = \array_merge(
\array_values(\array_diff_key(
$this->finderPlugin->getFound(),
$params
)),
$nodes['use'] ?? []
);
return new Closure($nodes, $func->getAttributes());
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Phabel\Target\Php74;
use Phabel\Plugin;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\AssignOp\Coalesce;
use PhpParser\Node\Expr\BinaryOp\Coalesce as BinaryOpCoalesce;
class NullCoalesceAssignment extends Plugin
{
public function enter(Coalesce $coalesce): Assign
{
return new Assign($coalesce->var, new BinaryOpCoalesce($coalesce->var, $coalesce->expr), $coalesce->getAttributes());
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Phabel\Target\Php74;
use Phabel\Plugin;
/**
*
*/
class TypedProperty extends Plugin
{
}

View File

@ -28,6 +28,23 @@ class Traverser
* @return SplQueue<SplQueue<Plugin>>
*/
private SplQueue $packageQueue;
/**
* Generate traverser from basic plugin instances.
*
* @param Plugin ...$plugin Plugins
*
* @return self
*/
public static function fromPlugin(Plugin ...$plugin): self
{
$queue = new SplQueue;
foreach ($plugin as $p) {
$queue->enqueue($p);
}
$final = new SplQueue;
$final->enqueue($queue);
return new self($final);
}
/**
* AST traverser.
*
@ -68,7 +85,7 @@ class Traverser
/**
* Traverse AST of file.
*
* @param string $file File
* @param string $file File
*
* @return void
*/
@ -95,9 +112,21 @@ class Traverser
return;
}
$ast = new RootNode($this->parser->parse(\file_get_contents($file)));
$this->traverseAst($ast, $reducedQueue);
}
/**
* Traverse AST.
*
* @param Node $node Initial node
* @param SplQueue $pluginQueue Plugin queue (optional)
*
* @return void
*/
public function traverseAst(Node &$node, SplQueue $pluginQueue = null): void
{
$context = new Context;
$context->parents->push($ast);
foreach ($reducedQueue as $queue) {
$context->parents->push($node);
foreach ($pluginQueue ?? $this->packageQueue ?? $this->queue as $queue) {
$this->traverseNode($ast, $queue, $context);
}
}

View File

@ -1,4 +0,0 @@
<?php
function a(): void {
}

View File

@ -1,6 +1,7 @@
<?php
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
require 'vendor/autoload.php';
@ -10,5 +11,7 @@ if ($argc < 2) {
}
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
//$parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP5);
\var_dump($parser->parse(\file_get_contents($argv[1])));
\var_dump($a = $parser->parse(\file_get_contents($argv[1])));
var_dumP((new Standard())->prettyPrint($a));

285
test/exprGen.php Normal file
View File

@ -0,0 +1,285 @@
<?php
use HaydenPierce\ClassFinder\ClassFinder;
use PhpParser\Builder\Class_;
use PhpParser\Builder\Method;
use PhpParser\Internal\PrintableNewAnonClassNode;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Error;
use PhpParser\Node\Expr\Exit_;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\List_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\EncapsedStringPart;
use PhpParser\Node\Stmt\Class_ as StmtClass_;
use PhpParser\Node\VarLikeIdentifier;
require 'vendor/autoload.php';
function format(Node $code): string
{
static $count = 0;
$count++;
$code = (new Class_("lmao$count"))->addStmt(
(new Method("te"))
->addStmt($code)
->getNode()
)->getNode();
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
return $prettyPrinter->prettyPrintFile([$code]);
}
function readUntilPrompt($resource): string
{
$data = '';
while (\substr($data, -6) !== 'php > ') {
$data .= \fread($resource, 1);
}
return \substr($data, 0, -6);
}
function checkSyntaxVersion(int $version, string $code): bool
{
$hasPrompt = $version < 80;
$code = \str_replace(["\n", '<?php'], '', $code)."\n";
static $processes = [];
static $pipes = [];
if (!isset($processes[$version])) {
$cmd = "php$version -a 2>&1";
$processes[$version] = \proc_open($cmd, [0 => ['pipe', 'r'], 1 => ['pipe', 'w']], $pipes[$version]);
if ($hasPrompt) {
readUntilPrompt($pipes[$version][1]);
} else {
\fgets($pipes[$version][1]);
\fgets($pipes[$version][1]);
}
}
if (!$hasPrompt) {
$code .= "echo 'php > ';\n";
}
\fputs($pipes[$version][0], $code);
if ($hasPrompt) {
$result = \str_replace(['{', '}'], '', \substr(\preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', readUntilPrompt($pipes[$version][1])), \strlen($code)));
//var_dump($code, "REsult for $version is: " .trim($result));
} else {
$result = readUntilPrompt($pipes[$version][1]);
//var_dump($code, trim($result));
}
$result = \trim($result);
return \strlen($result) === 0;
}
function checkSyntax(string $code, int $startFrom = 56): int
{
if (!$startFrom) {
return $startFrom;
}
foreach ([56, 70, 73, 74, 80] as $version) {
if ($version < $startFrom) {
continue;
}
if (checkSyntaxVersion($version, $code)) {
return $version;
}
}
return 0;
}
/** @var ReflectionClass[] */
$expressions = [];
foreach ($allClasses = ClassFinder::getClassesInNamespace('PhpParser', ClassFinder::RECURSIVE_MODE) as $class) {
$class = new ReflectionClass($class);
if ($class->isSubclassOf(Expr::class) && !$class->isAbstract()
&& $class->getName() !== PrintableNewAnonClassNode::class
&& $class->getName() !== ArrowFunction::class
&& $class->getName() !== Error::class
&& $class->getName() !== List_::class
&& $class->getName() !== ArrayItem::class
&& $class->getName() !== EncapsedStringPart::class
&& $class->getName() !== Exit_::class
&& $class->getName() !== List_::class) {
$expressions []= $class;
}
}
$instanceArgs = [];
$instanceArgNames = [];
$instanceArgTypes = [];
$exprInstances = [];
foreach ($expressions as $expr) {
$class = $expr->getName();
$method = $expr->getMethod('__construct');
if ($method->getNumberOfParameters() === 1) {
$exprInstances[$class] = $expr->newInstance();
continue; // Is a magic constant or such
}
\preg_match_all('/@param (?<type>\S+) +\$(?<name>\S+)/', $method->getDocComment(), $matches);
$types = \array_combine($matches['name'], $matches['type']);
foreach ($types as &$type) {
$type = \explode("|", $type);
foreach ($type as $key => &$subtype) {
if (str_starts_with($subtype, 'Node')) {
$subtype = 'PhpParser\\'.$subtype;
} elseif ($subtype === 'Error') {
unset($type[$key]);
} elseif ($subtype === 'Identifier') {
$subtype = Identifier::class;
} elseif ($subtype === 'Name') {
$subtype = Name::class;
} elseif ($subtype === 'Expr') {
$subtype = Expr::class;
} elseif ($subtype === 'VarLikeIdentifier') {
$subtype = VarLikeIdentifier::class;
}
}
}
$params = $method->getParameters();
$hasExpr = false;
$arguments = [];
$argNames = [];
$argTypes = [];
foreach ($params as $key => $param) {
$paramStr = (string) $param->getType();
$argNames[] = $param->getName();
switch ($paramStr) {
case Expr::class:
$argTypes[$key] = [false, [Expr::class]];
$arguments[] = new Variable("test");
break;
case Name::class:
$arguments[] = new Name('self');
break;
case Variable::class:
$arguments[] = new Variable("test");
break;
case 'array':
if (\in_array('Expr[]', $types[$param->getName()] ?? [])) {
$argTypes[$key] = [true, [Expr::class]];
$arguments[] = [new Variable('test')];
} else {
$arguments[] = [];
}
break;
case 'bool':
$arguments[] = false;
break;
case 'float':
case 'int':
$arguments[] = 0;
break;
case 'string':
$arguments[] = 'test';
break;
default:
$argTypes[$key] = [false, $types[$param->getName()]];
$arguments[] = new Variable("test");
break;
}
}
$exprInstances[$class] = $expr->newInstanceArgs($arguments);
if (\count($argTypes)) {
$instanceArgs[$class] = $arguments;
$instanceArgNames[$class] = $argNames;
$instanceArgTypes[$class] = $argTypes;
}
}
$versionMap = [];
$result = [
'main' => [], // Needs adaptation for nested expressions
'isset' => [], // Needs adaptation for nested expressions in isset
];
$newInstances = [];
foreach ($exprInstances as $class => $instance) {
$version = checkSyntax(format($prev = $instance));
$versionMap[$class] = $version ?: 1000;
}
foreach ($instanceArgTypes as $class => $argTypes) {
$baseArgs = $instanceArgs[$class];
foreach ($argTypes as $key => [$isArray, $types]) {
$name = $instanceArgNames[$class][$key];
$possibleValues = [];
foreach ($types as $type) {
switch ($type) {
case Expr::class:
$possibleValues = array_merge($possibleValues, $exprInstances);
break;
case Name::class:
$possibleValues[] = new Name('self');
break;
case Variable::class:
$possibleValues[] = new Variable("test");
break;
case Identifier::class;
$possibleValues[] = new Identifier('test');
break;
case StmtClass_::class:
// Avoid using anonymous classes
//$possibleValues[] = new StmtClass_(null);
break;
case 'string':
$possibleValues[] = 'test';
break;
default:
throw new Exception($type);
}
}
foreach ($possibleValues as $arg) {
$subVersion = \max(is_object($arg) ? $versionMap[\get_class($arg)] : 0, $versionMap[$class]);
$arguments = $baseArgs;
$arguments[$key] = $isArray ? [$arg] : $arg;
$code = format($prev = new $class(...$arguments));
$curVersion = checkSyntax($code, $subVersion);
if ($curVersion && $curVersion !== $subVersion) {
$result['main'][$curVersion][$class][$name][$arg] = true;
echo "Min $curVersion for $code\n";
}
$code = format(new Isset_([$prev]));
$curVersion = checkSyntax($code, $subVersion);
if ($curVersion && $curVersion !== $subVersion) {
$result['isset'][$curVersion][$class][$name][\get_class($expr)] = true;
echo "Min $curVersion for $code\n";
}
}
}
}
foreach ($instanceArgs as $class => $argumentsOld) {
foreach ($argumentsOld as $key => $argument) {
$name = $instanceArgNames[$class][$key];
if ($argument instanceof Expr || (\is_array($argument) && \count($argument))) {
foreach ($exprInstances as $expr) {
$subVersion = \max($versionMap[\get_class($expr)], $versionMap[$class]);
$arguments = $argumentsOld;
$arguments[$key] = \is_array($argument) ? [$expr] : $expr;
$code = format($prev = new $class(...$arguments));
$curVersion = checkSyntax($code, $subVersion);
if ($curVersion && $curVersion !== $subVersion) {
$result['main'][$curVersion][$class][$name][\get_class($expr)] = true;
echo "Min $curVersion for $code\n";
}
$code = format(new Isset_([$prev]));
$curVersion = checkSyntax($code, $subVersion);
if ($curVersion && $curVersion !== $subVersion) {
$result['isset'][$curVersion][$class][$name][\get_class($expr)] = true;
echo "Min $curVersion for $code\n";
}
}
}
}
}
$allClassesKeys = \array_fill_keys($allClasses, true);
\file_put_contents('result.php', '<?php $result = '.\var_export($result, true).";");