diff --git a/doc/filters/markdown_to_html.rst b/doc/filters/markdown_to_html.rst
index 8e3fede00..886cda4d4 100644
--- a/doc/filters/markdown_to_html.rst
+++ b/doc/filters/markdown_to_html.rst
@@ -7,7 +7,7 @@ The ``markdown_to_html`` filter converts a block of Markdown to HTML:
{% apply markdown_to_html %}
Title
- ======
+ =====
Hello!
{% endapply %}
@@ -19,7 +19,7 @@ removed consistently before conversion:
{% apply markdown_to_html %}
Title
- ======
+ =====
Hello!
{% endapply %}
diff --git a/doc/functions/attribute.rst b/doc/functions/attribute.rst
index e9d9a842e..832859de1 100644
--- a/doc/functions/attribute.rst
+++ b/doc/functions/attribute.rst
@@ -21,3 +21,10 @@ attribute:
The resolution algorithm is the same as the one used for the ``.``
notation, except that the item can be any valid expression.
+
+Arguments
+---------
+
+* ``variable``: The variable
+* ``attribute``: The attribute name
+* ``arguments``: An array of arguments to pass to the call
diff --git a/doc/functions/block.rst b/doc/functions/block.rst
index 117e160f5..efc516eb1 100644
--- a/doc/functions/block.rst
+++ b/doc/functions/block.rst
@@ -32,6 +32,12 @@ current template:
...
{% endif %}
+Arguments
+---------
+
+* ``name``: The block name
+* ``template``: The template where to look for the block
+
.. seealso::
:doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>`
diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php
index 99fd42382..7a04f5c9f 100644
--- a/src/ExpressionParser.php
+++ b/src/ExpressionParser.php
@@ -34,6 +34,7 @@ use Twig\Node\Expression\Unary\NegUnary;
use Twig\Node\Expression\Unary\NotUnary;
use Twig\Node\Expression\Unary\PosUnary;
use Twig\Node\Node;
+use Twig\Util\CallableArgumentsExtractor;
/**
* Parses expressions.
@@ -439,7 +440,6 @@ class ExpressionParser
{
switch ($name) {
case 'parent':
- $this->parseArguments();
if (!\count($this->parser->getBlockStack())) {
throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext());
}
@@ -448,21 +448,23 @@ class ExpressionParser
throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext());
}
+ $this->parseArguments(true);
+
return new ParentExpression($this->parser->peekBlockStack(), $line);
case 'block':
- $args = $this->parseArguments();
- if (\count($args) < 1) {
- throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext());
- }
+ $fakeNode = new Node(lineno: $line);
+ $fakeNode->setSourceContext($this->parser->getStream()->getSourceContext());
+ $fakeFunction = new TwigFunction('block', fn ($name, $template = null) => null);
+ $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($this->parseArguments(true));
- return new BlockReferenceExpression($args->getNode('0'), \count($args) > 1 ? $args->getNode('1') : null, $line);
+ return new BlockReferenceExpression($args[0], $args[1] ?? null, $line);
case 'attribute':
- $args = $this->parseArguments();
- if (\count($args) < 2) {
- throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext());
- }
+ $fakeNode = new Node(lineno: $line);
+ $fakeNode->setSourceContext($this->parser->getStream()->getSourceContext());
+ $fakeFunction = new TwigFunction('attribute', fn ($variable, $attribute, $arguments = null) => null);
+ $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($this->parseArguments(true));
- return new GetAttrExpression($args->getNode('0'), $args->getNode('1'), \count($args) > 2 ? $args->getNode('2') : null, Template::ANY_CALL, $line);
+ return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line);
case 'loop':
$args = $this->parseArguments();
if (\count($args) < 1) {
diff --git a/src/Util/CallableArgumentsExtractor.php b/src/Util/CallableArgumentsExtractor.php
index 1c8244642..1eb6a4524 100644
--- a/src/Util/CallableArgumentsExtractor.php
+++ b/src/Util/CallableArgumentsExtractor.php
@@ -121,7 +121,7 @@ final class CallableArgumentsExtractor
$optionalArguments = [];
++$pos;
} elseif ($callableParameter->isDefaultValueAvailable()) {
- $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1);
+ $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), $this->node->getTemplateLine());
} elseif ($callableParameter->isOptional()) {
if (!$extractedArguments) {
break;
@@ -134,12 +134,12 @@ final class CallableArgumentsExtractor
}
if ($this->twigCallable->isVariadic()) {
- $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1);
+ $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], $this->node->getTemplateLine()) : new ArrayExpression([], $this->node->getTemplateLine());
foreach ($extractedArguments as $key => $value) {
if (\is_int($key)) {
$arbitraryArguments->addElement($value);
} else {
- $arbitraryArguments->addElement($value, new ConstantExpression($key, -1));
+ $arbitraryArguments->addElement($value, new ConstantExpression($key, $this->node->getTemplateLine()));
}
unset($extractedArguments[$key]);
}
diff --git a/tests/Fixtures/functions/attribute.test b/tests/Fixtures/functions/attribute.test
index 4499ad4bd..2f70b08a4 100644
--- a/tests/Fixtures/functions/attribute.test
+++ b/tests/Fixtures/functions/attribute.test
@@ -2,17 +2,21 @@
"attribute" function
--TEMPLATE--
{{ attribute(obj, method) }}
+{{ attribute(variable=obj, attribute=method) }}
{{ attribute(array, item) }}
{{ attribute(obj, "bar", ["a", "b"]) }}
{{ attribute(obj, "bar", arguments) }}
+{{ attribute(variable=obj, attribute="bar", arguments=arguments) }}
{{ attribute(obj, method) is defined ? 'ok' : 'ko' }}
{{ attribute(obj, nonmethod) is defined ? 'ok' : 'ko' }}
--DATA--
return ['obj' => new Twig\Tests\TwigTestFoo(), 'method' => 'foo', 'array' => ['foo' => 'bar'], 'item' => 'foo', 'nonmethod' => 'xxx', 'arguments' => ['a', 'b']]
--EXPECT--
foo
+foo
bar
bar_a-b
bar_a-b
+bar_a-b
ok
ko
diff --git a/tests/Fixtures/functions/attribute_with_wrong_args.test b/tests/Fixtures/functions/attribute_with_wrong_args.test
new file mode 100644
index 000000000..6e8a17cf8
--- /dev/null
+++ b/tests/Fixtures/functions/attribute_with_wrong_args.test
@@ -0,0 +1,8 @@
+--TEST--
+"attribute" function
+--TEMPLATE--
+{{ attribute(var=var, template="tpl") }}
+--DATA--
+return ['var' => null]
+--EXCEPTION--
+Twig\Error\SyntaxError: Value for argument "variable" is required for function "attribute" in "index.twig" at line 2.
diff --git a/tests/Fixtures/functions/block.test b/tests/Fixtures/functions/block.test
index 1a4fd5492..bf8decd1e 100644
--- a/tests/Fixtures/functions/block.test
+++ b/tests/Fixtures/functions/block.test
@@ -5,8 +5,9 @@
{% block bar %}BAR{% endblock %}
--TEMPLATE(base.twig)--
{% block foo %}{{ block('bar') }}{% endblock %}
+{% block baz %}{{ block(name='bar') }}{% endblock %}
{% block bar %}BAR_BASE{% endblock %}
--DATA--
return []
--EXPECT--
-BARBAR
+BARBARBAR
diff --git a/tests/Fixtures/functions/block_with_template.test b/tests/Fixtures/functions/block_with_template.test
index 37cb7a481..2e0d2916c 100644
--- a/tests/Fixtures/functions/block_with_template.test
+++ b/tests/Fixtures/functions/block_with_template.test
@@ -6,6 +6,10 @@
{{ block('foo', included_loaded_internal) }}
{% set output = block('foo', 'included.twig') %}
{{ output }}
+{% set output = block(name='foo', template='included.twig') %}
+{{ output }}
+{% set output = block(template='included.twig', name='foo') %}
+{{ output }}
{% block foo %}NOT FOO{% endblock %}
--TEMPLATE(included.twig)--
{% block foo %}FOO{% endblock %}
@@ -19,4 +23,6 @@ FOO
FOO
FOO
FOO
+FOO
+FOO
NOT FOO
diff --git a/tests/Fixtures/functions/block_without_name.test b/tests/Fixtures/functions/block_without_name.test
index 236df9451..61896a8a2 100644
--- a/tests/Fixtures/functions/block_without_name.test
+++ b/tests/Fixtures/functions/block_without_name.test
@@ -9,4 +9,4 @@
--DATA--
return []
--EXCEPTION--
-Twig\Error\SyntaxError: The "block" function takes one argument (the block name) in "base.twig" at line 2.
+Twig\Error\SyntaxError: Value for argument "name" is required for function "block" in "base.twig" at line 2.