minor #3860 Fix spread operator implementation (nicolas-grekas)

This PR was merged into the 3.x branch.

Discussion
----------

Fix spread operator implementation

#3839 is too invasive, leading to failures like https://github.com/symfony/symfony/actions/runs/5612246672/jobs/10269741115

Commits
-------

75efa5e1 Fix spread operator implementation
This commit is contained in:
Nicolas Grekas
2023-07-20 18:36:01 +02:00
2 changed files with 57 additions and 65 deletions
+13 -8
View File
@@ -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')) {
+44 -57
View File
@@ -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;
}
}