Skip to content

Commit

Permalink
minor #3860 Fix spread operator implementation (nicolas-grekas)
Browse files Browse the repository at this point in the history
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
-------

75efa5e Fix spread operator implementation
  • Loading branch information
nicolas-grekas committed Jul 20, 2023
2 parents 09177a7 + 75efa5e commit 4ef92bf
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 65 deletions.
21 changes: 13 additions & 8 deletions src/Node/Expression/ArrayExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ public function addElement(AbstractExpression $value, AbstractExpression $key =
{
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);
Expand All @@ -70,15 +67,15 @@ public function addElement(AbstractExpression $value, AbstractExpression $key =
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(');
}
$compiler->raw('[');
$first = true;
$reopenAfterMergeSpread = false;
$nextIndex = 0;
foreach ($keyValuePairs as $pair) {
if ($reopenAfterMergeSpread) {
$compiler->raw(', [');
Expand All @@ -98,14 +95,22 @@ public function compile(Compiler $compiler): void

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']);
}
}
Expand All @@ -117,7 +122,7 @@ public function compile(Compiler $compiler): void
}
}

private function hasSpreadItem(array $pairs)
private function hasSpreadItem(array $pairs): bool
{
foreach ($pairs as $pair) {
if ($pair['value']->hasAttribute('spread')) {
Expand Down
101 changes: 44 additions & 57 deletions tests/ExpressionParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,106 +93,104 @@ public function getTestsForArray()
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),

$this->createConstantExpression(1, false),
$this->createConstantExpression(2),
], 1),
new ConstantExpression('a', 1),
new ArrayExpression([
new ConstantExpression(0, 1),
new ConstantExpression(1, 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)],
];
Expand Down Expand Up @@ -425,15 +423,4 @@ private function createNameExpression(string $name, array $attributes)

return $expression;
}

private function createConstantExpression($value, ?bool $indexSpecified = null)
{
$constant = new ConstantExpression($value, 1);

if (null !== $indexSpecified) {
$constant->setAttribute('index_specified', $indexSpecified);
}

return $constant;
}
}

0 comments on commit 4ef92bf

Please sign in to comment.