Repository: twigphp/Twig (php)

Issue:
Named arguments for macros
```
--TEST--
macro
--TEMPLATE--
{% from _self import test %}

{% macro test(a, b = 'bar') -%}
{{ a }}{{ b }}
{%- endmacro %}

{{ test('foo') }}
{{ test('bar', 'foo') }}
{{ test(b = 'bar', a = 'foo') }}
--DATA--
return array();
--EXPECT--
foobar
barfoo
foobar
```

Twig_Error_Syntax: Arguments must be separated by a comma. Unexpected token "operator" of value "=" ("punctuation" expected with value ",") in "C:\public_html\Twig\test\Twig\Tests\Fixtures\macros\default_values.test" at line 10

Named argument support
As @weaverryan and I have been working on Twig/Live components in `symfony/ux`, we've come across a few areas where named argument support would be valuable. This issue is intended to track/discuss adding/improving support for this in twig.

1. (probable bug) Filters/functions using `is_variadic` snake-cases the keys. This issue (and a possible solution) is explained fully in https://github.com/twigphp/Twig/issues/3475.
2. Support in `twig_get_attribute` for PHP8+
    ```twig
    {{ object.method(prop1='value', prop2='value') }}
    ```
3. Support in filters/functions without `is_variadic` for PHP8+
    ```twig
    {{ var|filter(prop1='value', prop2='value') }}

    {{ function(prop1='value', prop2='value') }}
    ```
4. Could `:` be used as an assignment operator instead of `=`?
    ```twig
    {{ var|filter(prop1: 'value', prop2: 'value') }}

    {{ function(prop1: 'value', prop2: 'value') }}

    {{ object.method(prop1: 'value', prop2:'value') }}
    ```

I understand there might be technical limitations that make supporting any/all of these impossible. This was the case with [named argument support in macros](https://github.com/twigphp/Twig/issues/929#issuecomment-493525160).

Pull request description:
Add support for named arguments on macro calls and dot operator arguments
Closes #929
Closes #3635


Patch:
diff --git a/CHANGELOG b/CHANGELOG
index ef500e8a..5b07b595 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,7 @@
 # 3.15.0 (2024-XX-XX)
 
+ * Add named arguments support for the dot operator arguments (`foo.bar(some: arg)`)
+ * Add named arguments support for macros
  * Add a new `guard` tag that allows to test if some Twig callables are available at compilation time
  * Allow arrow functions everywhere
  * Deprecate passing a string or an array to Twig callable arguments accepting arrow functions (pass a `\Closure`)
diff --git a/doc/tags/macro.rst b/doc/tags/macro.rst
index d1d0641c..13c03291 100644
--- a/doc/tags/macro.rst
+++ b/doc/tags/macro.rst
@@ -52,8 +52,8 @@ tag:
     {% import "forms.twig" as forms %}
 
 The above ``import`` call imports the ``forms.twig`` file (which can contain
-only macros, or a template and some macros), and import the macros as items of
-the ``forms`` local variable.
+only macros, or a template and some macros), and import the macros as
+attributes of the ``forms`` local variable.
 
 The macros can then be called at will in the *current* template:
 
@@ -61,6 +61,8 @@ The macros can then be called at will in the *current* template:
 
     <p>{{ forms.input('username') }}</p>
     <p>{{ forms.input('password', null, 'password') }}</p>
+    {# You can also use named arguments #}
+    <p>{{ forms.input(name: 'password', type: 'password') }}</p>
 
 Alternatively you can import names from the template into the current namespace
 via the ``from`` tag:
@@ -70,6 +72,7 @@ via the ``from`` tag:
     {% from 'forms.twig' import input as input_field, textarea %}
 
     <p>{{ input_field('password', '', 'password') }}</p>
+    <p>{{ input_field(name: 'password', type: 'password') }}</p>
     <p>{{ textarea('comment') }}</p>
 
 .. caution::
diff --git a/doc/templates.rst b/doc/templates.rst
index 54f26c4b..4fbbe2fe 100644
--- a/doc/templates.rst
+++ b/doc/templates.rst
@@ -225,7 +225,13 @@ built-in functions.
 Named Arguments
 ---------------
 
-Named arguments are supported in functions, filters, and tests.
+Named arguments are supported everywhere you can pass arguments: functions,
+filters, tests, macros, and dot operator arguments.
+
+.. versionadded:: 3.15
+
+    Named arguments for macros and dot operator arguments were added in Twig
+    3.15.
 
 .. versionadded:: 3.12
 
@@ -873,12 +879,15 @@ The following operators don't fit into any of the other categories:
 * ``.``, ``[]``: Gets an attribute of a variable.
 
   The (``.``) operator abstracts getting an attribute of a variable (methods,
- properties or constants of a PHP object, or items of a PHP array):
+  properties or constants of a PHP object, or items of a PHP array):
 
   .. code-block:: twig
 
       {{ user.name }}
 
+      Twig supports a specific syntax via the ``[]`` operator for accessing items
+      on sequences and mappings, like in ``user['name']``:
+
   After the ``.``, you can use any expression by wrapping it with parenthesis
   ``()``.
 
@@ -900,6 +909,22 @@ The following operators don't fit into any of the other categories:
   Before Twig 3.15, use the :doc:`attribute <functions/attribute>` function
   instead for the two previous use cases.
 
+  Twig supports a specific syntax via the ``[]`` operator for accessing items
+  on sequences and mappings:
+
+  .. code-block:: twig
+
+      {{ user['name'] }}
+
+  When calling a method, you can pass arguments using the ``()`` operator:
+
+  .. code-block:: twig
+
+      {{ html.generate_input() }}
+      {{ html.generate_input('pwd', 'password') }}
+      {# or using named arguments #}
+      {{ html.generate_input(name: 'pwd', type: 'password') }}
+
   .. sidebar:: PHP Implementation
 
       To resolve ``user.name`` to a PHP call, Twig uses the following algorithm
@@ -915,8 +940,8 @@ The following operators don't fit into any of the other categories:
       * if not, and if ``strict_variables`` is ``false``, return ``null``;
       * if not, throw an exception.
 
-      Twig supports a specific syntax via the ``[]`` operator for accessing items
-      on sequences and mappings, like in ``user['name']``:
+      To resolve ``user['name']`` to a PHP call, Twig uses the following algorithm
+      at runtime:
 
       * check if ``user`` is an array and ``name`` a valid element;
       * if not, and if ``strict_variables`` is ``false``, return ``null``;
diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php
index 56d6d4df..ddfbd2a4 100644
--- a/src/ExpressionParser.php
+++ b/src/ExpressionParser.php
@@ -26,6 +26,7 @@ use Twig\Node\Expression\ConstantExpression;
 use Twig\Node\Expression\GetAttrExpression;
 use Twig\Node\Expression\MethodCallExpression;
 use Twig\Node\Expression\NameExpression;
+use Twig\Node\Expression\TempNameExpression;
 use Twig\Node\Expression\TestExpression;
 use Twig\Node\Expression\Unary\AbstractUnary;
 use Twig\Node\Expression\Unary\NegUnary;
@@ -530,11 +531,7 @@ class ExpressionParser
     public function getFunctionNode($name, $line)
     {
         if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
-            $arguments = new ArrayExpression([], $line);
-            foreach ($this->parseArguments() as $n) {
-                $arguments->addElement($n);
-            }
-
+            $arguments = $this->createArguments($line);
             $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line);
             $node->setAttribute('safe', true);
 
@@ -575,9 +572,7 @@ class ExpressionParser
                 $stream->expect(Token::PUNCTUATION_TYPE, ')');
                 if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
                     $type = Template::METHOD_CALL;
-                    foreach ($this->parseArguments() as $n) {
-                        $arguments->addElement($n);
-                    }
+                    $arguments = $this->createArguments($lineno);
                 }
 
                 return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
@@ -594,9 +589,7 @@ class ExpressionParser
 
                 if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
                     $type = Template::METHOD_CALL;
-                    foreach ($this->parseArguments() as $n) {
-                        $arguments->addElement($n);
-                    }
+                    $arguments = $this->createArguments($lineno);
                 }
             } else {
                 throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());
@@ -708,6 +701,9 @@ class ExpressionParser
         if (func_num_args() > 2) {
             trigger_deprecation('twig/twig', '3.15', 'Passing a third argument ($allowArrow) to "%s()" is deprecated.', __METHOD__);
         }
+        if (!$namedArguments) {
+            trigger_deprecation('twig/twig', '3.15', 'Passing "false" for the first argument ($namedArguments) to "%s()" is deprecated.', __METHOD__);
+        }
 
         $args = [];
         $stream = $this->parser->getStream();
@@ -949,4 +945,14 @@ class ExpressionParser
 
         return $current;
     }
+
+    private function createArguments(int $line): ArrayExpression
+    {
+        $arguments = new ArrayExpression([], $line);
+        foreach ($this->parseArguments(true) as $k => $n) {
+            $arguments->addElement($n, new TempNameExpression($k, $line));
+        }
+
+        return $arguments;
+    }
 }
diff --git a/src/Lexer.php b/src/Lexer.php
index 4983313c..17547912 100644
--- a/src/Lexer.php
+++ b/src/Lexer.php
@@ -423,7 +423,7 @@ class Lexer
                 $result .= $nextChar;
             } elseif ("'" === $nextChar || '"' === $nextChar) {
                 if ($nextChar !== $quoteType) {
-                    trigger_deprecation('twig/twig', '3.12', 'Character "%s" should not be escaped; the "\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra "\" character at position %d in "%s" at line %d.', $nextChar, $i + 1, $this->source->getName(), $this->lineno);
+                    trigger_deprecation('twig/twig', '3.12', 'Character "%s" at position %d in string on line %d should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.', $nextChar, $i + 1, $this->lineno);
                 }
                 $result .= $nextChar;
             } elseif ('#' === $nextChar && $i + 1 < $length && '{' === $str[$i + 1]) {
@@ -442,7 +442,7 @@ class Lexer
                 }
                 $result .= \chr(octdec($octal));
             } else {
-                trigger_deprecation('twig/twig', '3.12', 'Character "%s" should not be escaped; the "\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra "\" character at position %d in "%s" at line %d.', $nextChar, $i + 1, $this->source->getName(), $this->lineno);
+                trigger_deprecation('twig/twig', '3.12', 'Character "%s" at position %d in string on line %d should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.', $nextChar, $i + 1, $this->lineno);
                 $result .= $nextChar;
             }
 
diff --git a/src/Node/Expression/ArrayExpression.php b/src/Node/Expression/ArrayExpression.php
index 6c6efee1..9769b719 100644
--- a/src/Node/Expression/ArrayExpression.php
+++ b/src/Node/Expression/ArrayExpression.php
@@ -98,10 +98,17 @@ class ArrayExpression extends AbstractExpression
                 $compiler->raw('...')->subcompile($pair['value']);
                 ++$nextIndex;
             } else {
-                $key = $pair['key'] instanceof ConstantExpression ? $pair['key']->getAttribute('value') : null;
+                $key = null;
                 if ($pair['key'] instanceof NameExpression) {
                     $pair['key'] = new StringCastUnary($pair['key'], $pair['key']->getTemplateLine());
                 }
+                if ($pair['key'] instanceof TempNameExpression) {
+                    $key = $pair['key']->getAttribute('name');
+                    $pair['key'] = new ConstantExpression($key, $pair['key']->getTemplateLine());
+                }
+                if ($pair['key'] instanceof ConstantExpression) {
+                    $key = $pair['key']->getAttribute('value');
+                }
 
                 if ($nextIndex !== $key) {
                     if (\is_int($key)) {
diff --git a/src/Node/Expression/GetAttrExpression.php b/src/Node/Expression/GetAttrExpression.php
index 29a446b8..571f6aea 100644
--- a/src/Node/Expression/GetAttrExpression.php
+++ b/src/Node/Expression/GetAttrExpression.php
@@ -18,6 +18,10 @@ use Twig\Template;
 
 class GetAttrExpression extends AbstractExpression
 {
+
+    /**
+     * @param ArrayExpression|NameExpression|null $arguments
+     */
     public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno)
     {
         $nodes = ['node' => $node, 'attribute' => $attribute];
@@ -25,6 +29,10 @@ class GetAttrExpression extends AbstractExpression
             $nodes['arguments'] = $arguments;
         }
 
+        if ($arguments && !$arguments instanceof ArrayExpression && !$arguments instanceof NameExpression) {
+            trigger_deprecation('twig/twig', '3.15', \sprintf('Not passing a "%s" instance as the "arguments" argument of the "%s" constructor is deprecated ("%s" given).', ArrayExpression::class, static::class, $arguments::class));
+        }
+
         parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno);
     }
 
diff --git a/src/Node/Expression/MethodCallExpression.php b/src/Node/Expression/MethodCallExpression.php
index 01806f91..1b337960 100644
--- a/src/Node/Expression/MethodCallExpression.php
+++ b/src/Node/Expression/MethodCallExpression.php
@@ -43,21 +43,9 @@ class MethodCallExpression extends AbstractExpression
             ->repr($this->getNode('node')->getAttribute('name'))
             ->raw('], ')
             ->repr($this->getAttribute('method'))
-            ->raw(', [')
-        ;
-        $first = true;
-        /** @var ArrayExpression */
-        $args = $this->getNode('arguments');
-        foreach ($args->getKeyValuePairs() as $pair) {
-            if (!$first) {
-                $compiler->raw(', ');
-            }
-            $first = false;
-
-            $compiler->subcompile($pair['value']);
-        }
-        $compiler
-            ->raw('], ')
+            ->raw(', ')
+            ->subcompile($this->getNode('arguments'))
+            ->raw(', ')
             ->repr($this->getTemplateLine())
             ->raw(', $context, $this->getSourceContext())');
     }
diff --git a/src/Node/Expression/TempNameExpression.php b/src/Node/Expression/TempNameExpression.php
index 004c704a..c1f09165 100644
--- a/src/Node/Expression/TempNameExpression.php
+++ b/src/Node/Expression/TempNameExpression.php
@@ -15,17 +15,21 @@ use Twig\Compiler;
 
 class TempNameExpression extends AbstractExpression
 {
-    public function __construct(string $name, int $lineno)
+    public const RESERVED_NAMES = ['varargs', 'context', 'macros', 'blocks', 'this'];
+
+    public function __construct(string|int $name, int $lineno)
     {
+        if (is_int($name) || ctype_digit($name)) {
+            $name = (int) $name;
+        } elseif (in_array($name, self::RESERVED_NAMES)) {
+            $name = '_'.$name.'_';
+        }
+
         parent::__construct([], ['name' => $name], $lineno);
     }
 
     public function compile(Compiler $compiler): void
     {
-        $compiler
-            ->raw('$_')
-            ->raw($this->getAttribute('name'))
-            ->raw('_')
-        ;
+        $compiler->raw('$'.$this->getAttribute('name'));
     }
 }
diff --git a/src/Node/MacroNode.php b/src/Node/MacroNode.php
index 5a2543a9..f3120dbd 100644
--- a/src/Node/MacroNode.php
+++ b/src/Node/MacroNode.php
@@ -14,6 +14,7 @@ namespace Twig\Node;
 use Twig\Attribute\YieldReady;
 use Twig\Compiler;
 use Twig\Error\SyntaxError;
+use Twig\Node\Expression\TempNameExpression;
 
 /**
  * Represents a macro node.
@@ -31,13 +32,17 @@ class MacroNode extends Node
     public function __construct(string $name, Node $body, Node $arguments, int $lineno)
     {
         if (!$body instanceof BodyNode) {
-            trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.', BodyNode::class, static::class));
+            trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated ("%s" given).', BodyNode::class, static::class, $body::class));
         }
 
         foreach ($arguments as $argumentName => $argument) {
             if (self::VARARGS_NAME === $argumentName) {
                 throw new SyntaxError(\sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine(), $argument->getSourceContext());
             }
+            if (in_array($argumentName, TempNameExpression::RESERVED_NAMES)) {
+                $arguments->setNode('_'.$argumentName.'_', $argument);
+                $arguments->removeNode($argumentName);
+            }
         }
 
         parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno);
@@ -54,7 +59,7 @@ class MacroNode extends Node
         $pos = 0;
         foreach ($this->getNode('arguments') as $name => $default) {
             $compiler
-                ->raw('$__'.$name.'__ = ')
+                ->raw('$'.$name.' = ')
                 ->subcompile($default)
             ;
 
@@ -68,7 +73,7 @@ class MacroNode extends Node
         }
 
         $compiler
-            ->raw('...$__varargs__')
+            ->raw('...$varargs')
             ->raw(")\n")
             ->write("{\n")
             ->indent()
@@ -80,8 +85,8 @@ class MacroNode extends Node
         foreach ($this->getNode('arguments') as $name => $default) {
             $compiler
                 ->write('')
-                ->string($name)
-                ->raw(' => $__'.$name.'__')
+                ->string(trim($name, '_'))
+                ->raw(' => $'.$name)
                 ->raw(",\n")
             ;
         }
@@ -92,7 +97,7 @@ class MacroNode extends Node
             ->write('')
             ->string(self::VARARGS_NAME)
             ->raw(' => ')
-            ->raw("\$__varargs__,\n")
+            ->raw("\$varargs,\n")
             ->outdent()
             ->write("] + \$this->env->getGlobals();\n\n")
             ->write("\$blocks = [];\n\n")


Test patch:
diff --git a/tests/ExpressionParserTest.php b/tests/ExpressionParserTest.php
index 82ff47d4..def632c1 100644
--- a/tests/ExpressionParserTest.php
+++ b/tests/ExpressionParserTest.php
@@ -275,24 +275,6 @@ class ExpressionParserTest extends TestCase
         ];
     }
 
-    public function testAttributeCallDoesNotSupportNamedArguments()
-    {
-        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);
-        $parser = new Parser($env);
-
-        $this->expectException(SyntaxError::class);
-        $parser->parse($env->tokenize(new Source('{{ foo.bar(name="Foo") }}', 'index')));
-    }
-
-    public function testMacroCallDoesNotSupportNamedArguments()
-    {
-        $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);
-        $parser = new Parser($env);
-
-        $this->expectException(SyntaxError::class);
-        $parser->parse($env->tokenize(new Source('{% from _self import foo %}{% macro foo() %}{% endmacro %}{{ foo(name="Foo") }}', 'index')));
-    }
-
     public function testMacroDefinitionDoesNotSupportNonNameVariableName()
     {
         $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);
diff --git a/tests/Fixtures/expressions/dynamic_attribute.test b/tests/Fixtures/expressions/dynamic_attribute.test
index 930c6f17..93731076 100644
--- a/tests/Fixtures/expressions/dynamic_attribute.test
+++ b/tests/Fixtures/expressions/dynamic_attribute.test
@@ -4,6 +4,9 @@
 {{ obj.(method) }}
 {{ array.(item) }}
 {{ obj.("bar")("a", "b") }}
+{{ obj.("bar")(param1: "a", param2: "b") }}
+{{ obj.("bar")(param2: "b", param1: "a") }}
+{{ obj.("bar")("a", param2: "b") }}
 {{ obj.("bar")(...arguments) }}
 {{ obj.(method) is defined ? 'ok' : 'ko' }}
 {{ obj.(nonmethod) is defined ? 'ok' : 'ko' }}
@@ -14,5 +17,8 @@ foo
 bar
 bar_a-b
 bar_a-b
+bar_a-b
+bar_a-b
+bar_a-b
 ok
 ko
diff --git a/tests/Fixtures/expressions/method_call.test b/tests/Fixtures/expressions/method_call.test
index bf49f389..ee700f80 100644
--- a/tests/Fixtures/expressions/method_call.test
+++ b/tests/Fixtures/expressions/method_call.test
@@ -6,6 +6,9 @@ Twig supports method calls
 {{ items.foo.bar }}
 {{ items.foo['bar'] }}
 {{ items.foo.bar('a', 43) }}
+{{ items.foo.bar(param1: 'a', param2: 43) }}
+{{ items.foo.bar(param2: 43, param1: 'a') }}
+{{ items.foo.bar('a', param2: 43) }}
 {{ items.foo.bar(foo) }}
 {{ items.foo.self.foo() }}
 {{ items.foo.is }}
@@ -20,6 +23,9 @@ foo
 foo
 bar
 
+bar_a-43
+bar_a-43
+bar_a-43
 bar_a-43
 bar_bar
 foo
diff --git a/tests/Fixtures/tags/macro/named_arguments.test b/tests/Fixtures/tags/macro/named_arguments.test
new file mode 100644
index 00000000..58bd15b2
--- /dev/null
+++ b/tests/Fixtures/tags/macro/named_arguments.test
@@ -0,0 +1,14 @@
+--TEST--
+"macro" tag
+--TEMPLATE--
+{% import _self as forms %}
+
+{{ forms.input(size: 10, name: 'username') }}
+
+{% macro input(name, value, type, size) %}
+  <input type="{{ type|default("text") }}" name="{{ name }}" value="{{ value|e|default('') }}" size="{{ size|default(20) }}">
+{% endmacro %}
+--DATA--
+return []
+--EXPECT--
+  <input type="text" name="username" value="" size="10">
diff --git a/tests/LexerTest.php b/tests/LexerTest.php
index d5640c8d..53a6cb15 100644
--- a/tests/LexerTest.php
+++ b/tests/LexerTest.php
@@ -281,7 +281,7 @@ class LexerTest extends TestCase
             {{ 'App\Test' }}
             EOF,
             'AppTest',
-            'Since twig/twig 3.12: Character "T" should not be escaped; the "\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra "\" character at position 5 in "index" at line 1.',
+            'Since twig/twig 3.12: Character "T" at position 5 in string on line 1 should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.',
         ];
         yield [
             <<<'EOF'
@@ -290,14 +290,14 @@ class LexerTest extends TestCase
             <<<'EOF'
             foo ' bar
             EOF,
-            'Since twig/twig 3.12: Character "\'" should not be escaped; the "\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra "\" character at position 6 in "index" at line 1.',
+            'Since twig/twig 3.12: Character "\'" at position 6 in string on line 1 should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.',
         ];
         yield [
             <<<'EOF'
             {{ 'foo \" bar' }}
             EOF,
             'foo " bar',
-            'Since twig/twig 3.12: Character """ should not be escaped; the "\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra "\" character at position 6 in "index" at line 1.',
+            'Since twig/twig 3.12: Character """ at position 6 in string on line 1 should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.',
         ];
     }
 
diff --git a/tests/Node/MacroTest.php b/tests/Node/MacroTest.php
index 7321c780..1902b73f 100644
--- a/tests/Node/MacroTest.php
+++ b/tests/Node/MacroTest.php
@@ -46,13 +46,13 @@ class MacroTest extends NodeTestCase
 
         yield 'with use_yield = true' => [$node, <<<EOF
 // line 1
-public function macro_foo(\$__foo__ = null, \$__bar__ = "Foo", ...\$__varargs__)
+public function macro_foo(\$foo = null, \$bar = "Foo", ...\$varargs)
 {
     \$macros = \$this->macros;
     \$context = [
-        "foo" => \$__foo__,
-        "bar" => \$__bar__,
-        "varargs" => \$__varargs__,
+        "foo" => \$foo,
+        "bar" => \$bar,
+        "varargs" => \$varargs,
     ] + \$this->env->getGlobals();
 
     \$blocks = [];
@@ -68,13 +68,13 @@ EOF
 
         yield 'with use_yield = false' => [$node, <<<EOF
 // line 1
-public function macro_foo(\$__foo__ = null, \$__bar__ = "Foo", ...\$__varargs__)
+public function macro_foo(\$foo = null, \$bar = "Foo", ...\$varargs)
 {
     \$macros = \$this->macros;
     \$context = [
-        "foo" => \$__foo__,
-        "bar" => \$__bar__,
-        "varargs" => \$__varargs__,
+        "foo" => \$foo,
+        "bar" => \$bar,
+        "varargs" => \$varargs,
     ] + \$this->env->getGlobals();
 
     \$blocks = [];