From 75efa5e1ba3ec676cab888a5984bafd3ca963461 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 20 Jul 2023 17:20:05 +0200 Subject: [PATCH] Fix spread operator implementation --- src/Node/Expression/ArrayExpression.php | 21 +++-- tests/ExpressionParserTest.php | 101 +++++++++++------------- 2 files changed, 57 insertions(+), 65 deletions(-) diff --git a/src/Node/Expression/ArrayExpression.php b/src/Node/Expression/ArrayExpression.php index 1b29dd19e..444283802 100644 --- a/src/Node/Expression/ArrayExpression.php +++ b/src/Node/Expression/ArrayExpression.php @@ -59,9 +59,6 @@ class ArrayExpression extends AbstractExpression { if (null === $key) { $key = new ConstantExpression(++$this->index, $value->getTemplateLine()); - $key->setAttribute('index_specified', false); - } else { - $key->setAttribute('index_specified', true); } array_push($this->nodes, $key, $value); @@ -70,8 +67,7 @@ class ArrayExpression extends AbstractExpression public function compile(Compiler $compiler): void { $keyValuePairs = $this->getKeyValuePairs(); - $hasSpreadItem = $this->hasSpreadItem($keyValuePairs); - $needsArrayMergeSpread = \PHP_VERSION_ID < 80100 && $hasSpreadItem; + $needsArrayMergeSpread = \PHP_VERSION_ID < 80100 && $this->hasSpreadItem($keyValuePairs); if ($needsArrayMergeSpread) { $compiler->raw('twig_array_merge('); @@ -79,6 +75,7 @@ class ArrayExpression extends AbstractExpression $compiler->raw('['); $first = true; $reopenAfterMergeSpread = false; + $nextIndex = 0; foreach ($keyValuePairs as $pair) { if ($reopenAfterMergeSpread) { $compiler->raw(', ['); @@ -98,14 +95,22 @@ class ArrayExpression extends AbstractExpression if ($pair['value']->hasAttribute('spread') && !$needsArrayMergeSpread) { $compiler->raw('...')->subcompile($pair['value']); + ++$nextIndex; } else { - $indexSpecified = false === $pair['key']->hasAttribute('index_specified') || true === $pair['key']->getAttribute('index_specified'); - if ($indexSpecified) { + $key = $pair['key'] instanceof ConstantExpression ? $pair['key']->getAttribute('value') : null; + + if ($nextIndex !== $key) { + if (\is_int($key)) { + $nextIndex = $key + 1; + } $compiler ->subcompile($pair['key']) ->raw(' => ') ; + } else { + ++$nextIndex; } + $compiler->subcompile($pair['value']); } } @@ -117,7 +122,7 @@ class ArrayExpression extends AbstractExpression } } - private function hasSpreadItem(array $pairs) + private function hasSpreadItem(array $pairs): bool { foreach ($pairs as $pair) { if ($pair['value']->hasAttribute('spread')) { diff --git a/tests/ExpressionParserTest.php b/tests/ExpressionParserTest.php index c6f34db0e..ab02296b6 100644 --- a/tests/ExpressionParserTest.php +++ b/tests/ExpressionParserTest.php @@ -93,106 +93,104 @@ class ExpressionParserTest extends TestCase return [ // simple array ['{{ [1, 2] }}', new ArrayExpression([ - $this->createConstantExpression(0, false), - $this->createConstantExpression(1), + new ConstantExpression(0, 1), + new ConstantExpression(1, 1), - $this->createConstantExpression(1, false), - $this->createConstantExpression(2), + new ConstantExpression(1, 1), + new ConstantExpression(2, 1), ], 1), ], // array with trailing , ['{{ [1, 2, ] }}', new ArrayExpression([ - $this->createConstantExpression(0, false), - $this->createConstantExpression(1), + new ConstantExpression(0, 1), + new ConstantExpression(1, 1), - $this->createConstantExpression(1, false), - $this->createConstantExpression(2), + new ConstantExpression(1, 1), + new ConstantExpression(2, 1), ], 1), ], // simple hash ['{{ {"a": "b", "b": "c"} }}', new ArrayExpression([ - $this->createConstantExpression('a', true), - $this->createConstantExpression('b'), + new ConstantExpression('a', 1), + new ConstantExpression('b', 1), - $this->createConstantExpression('b', true), - $this->createConstantExpression('c'), + new ConstantExpression('b', 1), + new ConstantExpression('c', 1), ], 1), ], // hash with trailing , ['{{ {"a": "b", "b": "c", } }}', new ArrayExpression([ - $this->createConstantExpression('a', true), - $this->createConstantExpression('b'), + new ConstantExpression('a', 1), + new ConstantExpression('b', 1), - $this->createConstantExpression('b', true), - $this->createConstantExpression('c'), + new ConstantExpression('b', 1), + new ConstantExpression('c', 1), ], 1), ], // hash in an array ['{{ [1, {"a": "b", "b": "c"}] }}', new ArrayExpression([ - $this->createConstantExpression(0, false), - $this->createConstantExpression(1), + new ConstantExpression(0, 1), + new ConstantExpression(1, 1), - $this->createConstantExpression(1, false), + new ConstantExpression(1, 1), new ArrayExpression([ - $this->createConstantExpression('a', true), - $this->createConstantExpression('b'), + new ConstantExpression('a', 1), + new ConstantExpression('b', 1), - $this->createConstantExpression('b', true), - $this->createConstantExpression('c'), + new ConstantExpression('b', 1), + new ConstantExpression('c', 1), ], 1), ], 1), ], // array in a hash ['{{ {"a": [1, 2], "b": "c"} }}', new ArrayExpression([ - $this->createConstantExpression('a', true), - new ArrayExpression([ - $this->createConstantExpression(0, false), - $this->createConstantExpression(1), + new ConstantExpression('a', 1), + new ArrayExpression([ + new ConstantExpression(0, 1), + new ConstantExpression(1, 1), - $this->createConstantExpression(1, false), - $this->createConstantExpression(2), - ], 1), - - $this->createConstantExpression('b', true), - $this->createConstantExpression('c'), + new ConstantExpression(1, 1), + new ConstantExpression(2, 1), + ], 1), + new ConstantExpression('b', 1), + new ConstantExpression('c', 1), ], 1), ], ['{{ {a, b} }}', new ArrayExpression([ - $this->createConstantExpression('a', true), + new ConstantExpression('a', 1), new NameExpression('a', 1), - - $this->createConstantExpression('b', true), + new ConstantExpression('b', 1), new NameExpression('b', 1), ], 1)], // array with spread operator ['{{ [1, 2, ...foo] }}', new ArrayExpression([ - $this->createConstantExpression(0, false), - $this->createConstantExpression(1), + new ConstantExpression(0, 1), + new ConstantExpression(1, 1), - $this->createConstantExpression(1, false), - $this->createConstantExpression(2), + new ConstantExpression(1, 1), + new ConstantExpression(2, 1), - $this->createConstantExpression(2, false), + new ConstantExpression(2, 1), $this->createNameExpression('foo', ['spread' => true]), ], 1)], // hash with spread operator ['{{ {"a": "b", "b": "c", ...otherLetters} }}', new ArrayExpression([ - $this->createConstantExpression('a', true), - $this->createConstantExpression('b'), + new ConstantExpression('a', 1), + new ConstantExpression('b', 1), - $this->createConstantExpression('b', true), - $this->createConstantExpression('c'), + new ConstantExpression('b', 1), + new ConstantExpression('c', 1), - $this->createConstantExpression(0, false), + new ConstantExpression(0, 1), $this->createNameExpression('otherLetters', ['spread' => true]), ], 1)], ]; @@ -425,15 +423,4 @@ class ExpressionParserTest extends TestCase return $expression; } - - private function createConstantExpression($value, ?bool $indexSpecified = null) - { - $constant = new ConstantExpression($value, 1); - - if (null !== $indexSpecified) { - $constant->setAttribute('index_specified', $indexSpecified); - } - - return $constant; - } }