diff --git a/CHANGELOG b/CHANGELOG index 3599adb98..1d6b388e3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ * Fix duplicated macro argument names triggering a PHP fatal error instead of a `SyntaxError` * Deprecate defining a macro more than once in the same template * Deprecate `TemplateVariable` and `AssignTemplateVariable`; use `MacroVariable` and `AssignMacroVariable` instead + * Deprecate omitting parentheses when calling a macro; it will throw a `SyntaxError` in 4.0 # 3.28.0 (2026-07-03) diff --git a/doc/deprecated.rst b/doc/deprecated.rst index b26c228d9..e3d2c60ca 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -318,6 +318,12 @@ Macros 3.29 and will throw a ``SyntaxError`` in Twig 4.0. Give each macro a unique name. +* Omitting parentheses when calling a macro (e.g. ``macros.input`` or + ``macros.(name)``) is deprecated as of Twig 3.29 and will throw a + ``SyntaxError`` in Twig 4.0. Add parentheses after the macro name (e.g. + ``macros.input()`` or ``macros.(name)()``). Parentheses remain optional when + testing whether a macro is defined. + Filters ------- diff --git a/src/ExpressionParser/Infix/DotExpressionParser.php b/src/ExpressionParser/Infix/DotExpressionParser.php index c299cf112..e7642ab91 100644 --- a/src/ExpressionParser/Infix/DotExpressionParser.php +++ b/src/ExpressionParser/Infix/DotExpressionParser.php @@ -77,11 +77,17 @@ final class DotExpressionParser extends AbstractExpressionParser implements Infi && \is_string($name = $attribute->getAttribute('value')) && preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $name) ) { - return new MacroReferenceExpression(new MacroVariable($expr->getAttribute('name'), $expr->getTemplateLine()), 'macro_'.$name, $arguments, $expr->getTemplateLine()); + $node = new MacroReferenceExpression(new MacroVariable($expr->getAttribute('name'), $expr->getTemplateLine()), 'macro_'.$name, $arguments, $expr->getTemplateLine()); + $node->setHasParentheses(Template::METHOD_CALL === $type); + + return $node; } if ($isMacroTarget && !$attribute instanceof ConstantExpression) { - return new MacroReferenceExpression(new MacroVariable($expr->getAttribute('name'), $expr->getTemplateLine()), $attribute, $arguments, $expr->getTemplateLine()); + $node = new MacroReferenceExpression(new MacroVariable($expr->getAttribute('name'), $expr->getTemplateLine()), $attribute, $arguments, $expr->getTemplateLine()); + $node->setHasParentheses(Template::METHOD_CALL === $type); + + return $node; } return new GetAttrExpression($expr, $attribute, $arguments, $type, $lineno, $nullSafe); diff --git a/src/Node/Expression/MacroReferenceExpression.php b/src/Node/Expression/MacroReferenceExpression.php index 8a813692e..010a54b8f 100644 --- a/src/Node/Expression/MacroReferenceExpression.php +++ b/src/Node/Expression/MacroReferenceExpression.php @@ -25,6 +25,8 @@ class MacroReferenceExpression extends AbstractExpression implements SupportDefi use SupportDefinedTestDeprecationTrait; use SupportDefinedTestTrait; + private bool $hasParentheses = true; + /** * @param string|AbstractExpression $name A static macro method name (e.g. "macro_foo") or, for a dynamic * call, an expression resolving to the macro name (without the @@ -51,6 +53,14 @@ class MacroReferenceExpression extends AbstractExpression implements SupportDefi parent::__construct($nodes, $attributes, $lineno); } + /** + * @internal + */ + public function setHasParentheses(bool $hasParentheses): void + { + $this->hasParentheses = $hasParentheses; + } + public function __clone() { // The template node must not be deep-cloned because its name is @@ -63,6 +73,10 @@ class MacroReferenceExpression extends AbstractExpression implements SupportDefi public function compile(Compiler $compiler): void { + if (!$this->hasParentheses && !$this->definedTest) { + trigger_deprecation('twig/twig', '3.29', 'Omitting parentheses when calling a macro is deprecated and will throw a SyntaxError in Twig 4.0; add parentheses after the macro name in "%s" at line %d.', $this->getTemplateName(), $this->getTemplateLine()); + } + if ($this->hasNode('name')) { $this->compileDynamic($compiler); diff --git a/tests/Fixtures/macros/call_without_parentheses.legacy.test b/tests/Fixtures/macros/call_without_parentheses.legacy.test new file mode 100644 index 000000000..bdb93d656 --- /dev/null +++ b/tests/Fixtures/macros/call_without_parentheses.legacy.test @@ -0,0 +1,16 @@ +--TEST-- +Omitting parentheses when calling macros is deprecated +--DEPRECATION-- +Since twig/twig 3.29: Omitting parentheses when calling a macro is deprecated and will throw a SyntaxError in Twig 4.0; add parentheses after the macro name in "index.twig" at line 4. +Since twig/twig 3.29: Omitting parentheses when calling a macro is deprecated and will throw a SyntaxError in Twig 4.0; add parentheses after the macro name in "index.twig" at line 5. +--TEMPLATE-- +{% import _self as macros %} +{% set name = 'hello' %} +{{ macros.hello }} +{{ _self.(name) }} +{% macro hello() %}Hello{% endmacro %} +--DATA-- +return [] +--EXPECT-- +Hello +Hello