diff --git a/CHANGELOG b/CHANGELOG index 89f4289bf..ec47be489 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.15.0 (2024-XX-XX) + * Add support for argument unpackaging * Add JSON support for the file extension escaping strategy * Support Markup instances (and any other \Stringable) as dynamic mapping keys * Deprecate the `sandbox` tag diff --git a/doc/templates.rst b/doc/templates.rst index 3cd8f9b90..199d40fe4 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -815,14 +815,21 @@ The following operators don't fit into any of the other categories: {# returns the value of foo if it is defined and not null, 'no' otherwise #} {{ foo ?? 'no' }} -* ``...``: The spread operator can be used to expand sequences or mappings (it - cannot be used to expand the arguments of a function call): +* ``...``: The spread operator can be used to expand sequences or mappings or + to expand the arguments of a function call: .. code-block:: twig {% set numbers = [1, 2, ...moreNumbers] %} {% set ratings = {'foo': 10, 'bar': 5, ...moreRatings} %} + {{ 'Hello %s %s!'|format(...['Fabien', 'Potencier']) }} + + .. versionadded:: 3.15 + + Support for expanding the arguments of a function call was introduced in + Twig 3.15. + Operators ~~~~~~~~~ diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index c77e09305..37e94c6ba 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -30,6 +30,7 @@ use Twig\Node\Expression\Unary\AbstractUnary; use Twig\Node\Expression\Unary\NegUnary; use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; +use Twig\Node\Expression\Unary\SpreadUnary; use Twig\Node\Node; /** @@ -618,6 +619,7 @@ class ExpressionParser $stream = $this->parser->getStream(); $stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + $hasSpread = false; while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) { if ($args) { $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); @@ -632,7 +634,14 @@ class ExpressionParser $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name'); $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine()); } else { - $value = $this->parseExpression(0, $allowArrow); + if ($stream->nextIf(Token::SPREAD_TYPE)) { + $hasSpread = true; + $value = new SpreadUnary($this->parseExpression(0, $allowArrow), $stream->getCurrent()->getLine()); + } elseif ($hasSpread) { + throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } else { + $value = $this->parseExpression(0, $allowArrow); + } } $name = null; diff --git a/src/Node/Expression/Unary/SpreadUnary.php b/src/Node/Expression/Unary/SpreadUnary.php new file mode 100644 index 000000000..f99072c25 --- /dev/null +++ b/src/Node/Expression/Unary/SpreadUnary.php @@ -0,0 +1,22 @@ +raw('...'); + } +} diff --git a/tests/Fixtures/expressions/call_argument_unpacking.test b/tests/Fixtures/expressions/call_argument_unpacking.test new file mode 100644 index 000000000..8e92310bf --- /dev/null +++ b/tests/Fixtures/expressions/call_argument_unpacking.test @@ -0,0 +1,16 @@ +--TEST-- +Twig supports array unpacking for function calls +--TEMPLATE-- +{{ '%s %s %s'|format(...[1, 2, 3]) }} +{{ '%s %s %s'|format(...[1], ...[2, 3]) }} +{{ '%s %s %s'|format(1, ...[2, 3]) }} +{{ '%s %s %s'|format(1, ...[2], ...[3]) }} +{{ '%s %s %s'|format(...it) }} +--DATA-- +return ['it' => new \ArrayIterator([1, 2, 3])] +--EXPECT-- +1 2 3 +1 2 3 +1 2 3 +1 2 3 +1 2 3 diff --git a/tests/Fixtures/expressions/call_argument_unpacking_before_normal.test b/tests/Fixtures/expressions/call_argument_unpacking_before_normal.test new file mode 100644 index 000000000..24259393f --- /dev/null +++ b/tests/Fixtures/expressions/call_argument_unpacking_before_normal.test @@ -0,0 +1,8 @@ +--TEST-- +Twig supports array unpacking for function calls (but not before normal args) +--TEMPLATE-- +{{ '%s %s %s'|format(...[1, 2], 3) }} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Normal arguments must be placed before argument unpacking in "index.twig" at line 2.