diff --git a/CHANGELOG b/CHANGELOG index c4798b911..39b5278dd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ * Fix `IntlExtension` letting the pattern derived from a date formatter prototype override an explicit locale * Fix `IntlExtension` not honoring the locale of a date formatter prototype configured with no date and time styles + * Speed up macro calls * Report a clear error when `random`, `reverse`, `shuffle`, and `split` receive a string that is not valid UTF-8 * Fix the deprecation about omitting parentheses when calling a macro being reported twice for the same call * Add the macro name to the deprecation about omitting parentheses when calling a macro diff --git a/src/MacroNamespace.php b/src/MacroNamespace.php index db66dc3b8..13ca706bc 100644 --- a/src/MacroNamespace.php +++ b/src/MacroNamespace.php @@ -53,6 +53,10 @@ final class MacroNamespace */ public function call(string $name, array $arguments, array $context, int $line, Source $source): string|Markup { + if (null !== $macro = $this->getDeclared($name)) { + return $macro->callLegacy($arguments, $source, $line); + } + if (null === $macro = $this->resolve($name, $context)) { if (!str_starts_with($name, 'macro_') || null === $macro = $this->resolve($bareName = substr($name, \strlen('macro_')), $context)) { throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".', $name, $this->template->getTemplateName()), $line, $source); diff --git a/src/Node/Expression/MacroReferenceExpression.php b/src/Node/Expression/MacroReferenceExpression.php index 168b762e5..26d1fe65b 100644 --- a/src/Node/Expression/MacroReferenceExpression.php +++ b/src/Node/Expression/MacroReferenceExpression.php @@ -90,7 +90,7 @@ class MacroReferenceExpression extends AbstractExpression implements SupportDefi ->subcompile($this->getNode('arguments')) ->raw(', $context, ') ->repr($this->getTemplateLine()) - ->raw(', $this->getSourceContext())') + ->raw(', $this->source)') ; } diff --git a/src/TwigMacro.php b/src/TwigMacro.php index 70e11e7c1..222f6d12b 100644 --- a/src/TwigMacro.php +++ b/src/TwigMacro.php @@ -100,6 +100,11 @@ final class TwigMacro return ($this->body)(...$arguments); } + // The keys are then exactly the declared names. + if (\count($arguments) === \count($this->arguments) && !array_diff_key($arguments, $this->argumentIndexes)) { + return ($this->body)(...$this->renameArguments($arguments)); + } + $positionalCount = 0; $namedRequired = 0; $sawNamed = false; @@ -142,6 +147,16 @@ final class TwigMacro $this->triggerLegacyDeprecations($arguments, $positionalCount, $source, $lineno); } + return ($this->body)(...$this->renameArguments($arguments)); + } + + /** + * @param array $arguments + * + * @return array + */ + private function renameArguments(array $arguments): array + { foreach ($this->renamedArguments as $name => $parameterName) { if (\array_key_exists($name, $arguments)) { $arguments[$parameterName] = $arguments[$name]; @@ -149,7 +164,7 @@ final class TwigMacro } } - return ($this->body)(...$arguments); + return $arguments; } /** diff --git a/tests/Node/Expression/MacroReferenceTest.php b/tests/Node/Expression/MacroReferenceTest.php index ecf60a774..3d8655950 100644 --- a/tests/Node/Expression/MacroReferenceTest.php +++ b/tests/Node/Expression/MacroReferenceTest.php @@ -68,7 +68,7 @@ class MacroReferenceTest extends TestCase $compiler->compile($node); $this->assertStringContainsString('->call(', $compiler->getSource()); - $this->assertStringContainsString('($context["name"] ?? null), [], $context, 1, $this->getSourceContext())', $compiler->getSource()); + $this->assertStringContainsString('($context["name"] ?? null), [], $context, 1, $this->source)', $compiler->getSource()); $this->assertStringNotContainsString("'macro_'.", $compiler->getSource()); } } diff --git a/tests/TwigMacroTest.php b/tests/TwigMacroTest.php index c1f7686a0..6c2172a39 100644 --- a/tests/TwigMacroTest.php +++ b/tests/TwigMacroTest.php @@ -134,6 +134,39 @@ class TwigMacroTest extends TestCase ['Since twig/twig 3.29: Passing the unknown named argument "extra" to the macro "test" is deprecated and will throw in Twig 4.0; declare a variadic argument ("...name") in the macro definition to accept it (in "index.twig" at line 7).'], ]; + yield 'unknown named argument is still reported when the argument count matches the signature' => [ + ['name' => false, 'value' => false], + false, + static function (&$captured) { + return static function ($name = null, $value = null, ...$varargs) use (&$captured) { + $captured = [$name, $value, $varargs]; + + return ''; + }; + }, + ['name' => 'a', 'extra' => 'b'], + ['a', null, ['extra' => 'b']], + [ + 'Since twig/twig 3.29: Not passing a value for the "value" argument of macro "test" is deprecated and the argument will be required in Twig 4.0; give it a default value in the macro definition or pass a value when calling it (in "index.twig" at line 7).', + 'Since twig/twig 3.29: Passing the unknown named argument "extra" to the macro "test" is deprecated and will throw in Twig 4.0; declare a variadic argument ("...name") in the macro definition to accept it (in "index.twig" at line 7).', + ], + ]; + + yield 'named arguments covering the whole signature bind to their parameter, reserved names included' => [ + ['context' => false, 'value' => false], + false, + static function (&$captured) { + return static function ($͜context = null, $value = null, ...$varargs) use (&$captured) { + $captured = [$͜context, $value, $varargs]; + + return ''; + }; + }, + ['value' => 'b', 'context' => 'a'], + ['a', 'b', []], + [], + ]; + yield 'null named argument value satisfies a required argument' => [ ['name' => false], false,