From fb9dac3d954c9adea8240d5f23fc060ddcc9a3ba Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 30 Aug 2020 16:58:59 +0200 Subject: [PATCH] Add expression generator --- composer.json | 3 +- src/Plugin/Amp/YieldReturnDetector.php | 32 --- src/Plugin/Amp/YieldReturnReplacer.php | 55 ---- src/Plugin/ReGenerator.php | 12 +- src/Plugin/ReGenerator/ReGenerator.php | 112 ++++---- src/Plugin/ReGeneratorInternal.php | 46 ++++ src/Plugin/TypeHintStripper.php | 4 +- src/Target/Php70/CompoundAccess.php | 13 +- src/Target/Php71/ListKey.php | 3 +- src/Target/Php72.php | 8 + src/Target/Php72/ObjectTypeHintReplacer.php | 23 ++ src/Target/Php74/ArrayUnpack.php | 43 +++ src/Target/Php74/ArrowClosure.php | 47 +++- src/Target/Php74/NullCoalesceAssignment.php | 16 ++ src/Target/Php74/TypedProperty.php | 13 + src/Traverser.php | 35 ++- test.php | 4 - test/dump.php | 5 +- test/exprGen.php | 285 ++++++++++++++++++++ 19 files changed, 594 insertions(+), 165 deletions(-) delete mode 100644 src/Plugin/Amp/YieldReturnDetector.php delete mode 100644 src/Plugin/Amp/YieldReturnReplacer.php create mode 100644 src/Plugin/ReGeneratorInternal.php create mode 100644 src/Target/Php72/ObjectTypeHintReplacer.php create mode 100644 src/Target/Php74/ArrayUnpack.php create mode 100644 src/Target/Php74/NullCoalesceAssignment.php create mode 100644 src/Target/Php74/TypedProperty.php delete mode 100644 test.php create mode 100644 test/exprGen.php diff --git a/composer.json b/composer.json index d44297f..d00a7b7 100644 --- a/composer.json +++ b/composer.json @@ -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": [{ diff --git a/src/Plugin/Amp/YieldReturnDetector.php b/src/Plugin/Amp/YieldReturnDetector.php deleted file mode 100644 index dd2c537..0000000 --- a/src/Plugin/Amp/YieldReturnDetector.php +++ /dev/null @@ -1,32 +0,0 @@ -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); - } - } -} diff --git a/src/Plugin/Amp/YieldReturnReplacer.php b/src/Plugin/Amp/YieldReturnReplacer.php deleted file mode 100644 index 7920031..0000000 --- a/src/Plugin/Amp/YieldReturnReplacer.php +++ /dev/null @@ -1,55 +0,0 @@ -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; - } -} diff --git a/src/Plugin/ReGenerator.php b/src/Plugin/ReGenerator.php index 8bf12dc..4990943 100644 --- a/src/Plugin/ReGenerator.php +++ b/src/Plugin/ReGenerator.php @@ -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 { diff --git a/src/Plugin/ReGenerator/ReGenerator.php b/src/Plugin/ReGenerator/ReGenerator.php index 8f061f6..fb93601 100644 --- a/src/Plugin/ReGenerator/ReGenerator.php +++ b/src/Plugin/ReGenerator/ReGenerator.php @@ -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; diff --git a/src/Plugin/ReGeneratorInternal.php b/src/Plugin/ReGeneratorInternal.php new file mode 100644 index 0000000..e96a689 --- /dev/null +++ b/src/Plugin/ReGeneratorInternal.php @@ -0,0 +1,46 @@ +> + */ + 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) + { + + } +} diff --git a/src/Plugin/TypeHintStripper.php b/src/Plugin/TypeHintStripper.php index 5675762..a922e03 100644 --- a/src/Plugin/TypeHintStripper.php +++ b/src/Plugin/TypeHintStripper.php @@ -57,12 +57,14 @@ class TypeHintStripper extends Plugin * @var SplStack */ 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); } } /** diff --git a/src/Target/Php70/CompoundAccess.php b/src/Target/Php70/CompoundAccess.php index ac044c8..9dbe718 100644 --- a/src/Target/Php70/CompoundAccess.php +++ b/src/Target/Php70/CompoundAccess.php @@ -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); } } /** diff --git a/src/Target/Php71/ListKey.php b/src/Target/Php71/ListKey.php index 4455574..063c23f 100644 --- a/src/Target/Php71/ListKey.php +++ b/src/Target/Php71/ListKey.php @@ -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]; } /** diff --git a/src/Target/Php72.php b/src/Target/Php72.php index 22999af..79d6573 100644 --- a/src/Target/Php72.php +++ b/src/Target/Php72.php @@ -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 + ]; + } } diff --git a/src/Target/Php72/ObjectTypeHintReplacer.php b/src/Target/Php72/ObjectTypeHintReplacer.php new file mode 100644 index 0000000..a9b9526 --- /dev/null +++ b/src/Target/Php72/ObjectTypeHintReplacer.php @@ -0,0 +1,23 @@ + [ + 'types' => ['object'] + ] + ]; + } +} diff --git a/src/Target/Php74/ArrayUnpack.php b/src/Target/Php74/ArrayUnpack.php new file mode 100644 index 0000000..5eab0da --- /dev/null +++ b/src/Target/Php74/ArrayUnpack.php @@ -0,0 +1,43 @@ +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); + } +} diff --git a/src/Target/Php74/ArrowClosure.php b/src/Target/Php74/ArrowClosure.php index ad53e32..fe132a3 100644 --- a/src/Target/Php74/ArrowClosure.php +++ b/src/Target/Php74/ArrowClosure.php @@ -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()); } } diff --git a/src/Target/Php74/NullCoalesceAssignment.php b/src/Target/Php74/NullCoalesceAssignment.php new file mode 100644 index 0000000..9aca7e8 --- /dev/null +++ b/src/Target/Php74/NullCoalesceAssignment.php @@ -0,0 +1,16 @@ +var, new BinaryOpCoalesce($coalesce->var, $coalesce->expr), $coalesce->getAttributes()); + } +} diff --git a/src/Target/Php74/TypedProperty.php b/src/Target/Php74/TypedProperty.php new file mode 100644 index 0000000..a10f67e --- /dev/null +++ b/src/Target/Php74/TypedProperty.php @@ -0,0 +1,13 @@ +> */ 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); } } diff --git a/test.php b/test.php deleted file mode 100644 index 9769a2c..0000000 --- a/test.php +++ /dev/null @@ -1,4 +0,0 @@ -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)); diff --git a/test/exprGen.php b/test/exprGen.php new file mode 100644 index 0000000..a7b240d --- /dev/null +++ b/test/exprGen.php @@ -0,0 +1,285 @@ +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", '&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 (?\S+) +\$(?\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', '