Improve macro call performance

This commit is contained in:
Fabien Potencier
2026-09-18 18:29:34 +01:00
parent 66406ea727
commit a8ee6dd762
6 changed files with 56 additions and 3 deletions
+1
View File
@@ -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
+4
View File
@@ -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);
@@ -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)')
;
}
+16 -1
View File
@@ -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<int|string, mixed> $arguments
*
* @return array<int|string, mixed>
*/
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;
}
/**
+1 -1
View File
@@ -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());
}
}
+33
View File
@@ -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,