From 816c510cef96d99cf489b08f742992c4fdcb0b03 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 29 Sep 2024 22:59:41 +0200 Subject: [PATCH] =?UTF-8?q?Deprecate=20using=20~=C2=A0with=20+=20or=20-=20?= =?UTF-8?q?in=20an=20expression=20without=20using=20parentheses=20to=20cla?= =?UTF-8?q?rify=20precedence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 1 + doc/deprecated.rst | 21 +++++++++++++++++++ src/ExpressionParser.php | 14 +++++++++++++ src/Extension/CoreExtension.php | 1 + .../Fixtures/operators/concat_vs_add_sub.test | 16 ++++++++++++++ .../operators/contat_vs_add_sub.legacy.test | 13 ++++++++++++ 6 files changed, 66 insertions(+) create mode 100644 tests/Fixtures/operators/concat_vs_add_sub.test create mode 100644 tests/Fixtures/operators/contat_vs_add_sub.legacy.test diff --git a/CHANGELOG b/CHANGELOG index f4a07a9c7..6100fefe9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.15.0 (2024-XX-XX) + * Deprecate using `~` with `+` or `-` in an expression without using parentheses to clarify precedence * Deprecate not passing `AbstractExpression` args to most constructor arguments for classes extending `AbstractExpression` * Fix `power` expressions with a negative number in parenthesis (`(-1) ** 2`) * Deprecate instantiating `Node` directly. Use `EmptyNode` or `Nodes` instead. diff --git a/doc/deprecated.rst b/doc/deprecated.rst index afcec62f5..10cc1214c 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -310,3 +310,24 @@ Node * ``DefaultFilter`` * ``InlinePrint`` * ``NullCoalesceExpression`` + +Operators +--------- + +* Using ``~`` with ``+`` or ``-`` in an expression without using parentheses to + clarify precedence is deprecated as of Twig 3.15 (in Twig 4.0, parentheses + won't be needed anymore as ``+`` / ``-`` will have a higher precedence than + ``~``). + + For example, the following expression will trigger a deprecation in Twig 3.15:: + + {{ '42' ~ 1 + 41 }} + + To avoid the deprecation, wrap the concatenation in parentheses to clarify + the precedence:: + + {{ ('42' ~ 1) + 41 }} {# this is equivalent to what Twig 3.x does without the parentheses #} + + {# or #} + + {{ '42' ~ (1 + 41) }} {# this is equivalent to what Twig 4.x will do without the parentheses #} diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index 33da2b29e..db6b15550 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -20,7 +20,9 @@ use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ArrowFunctionExpression; use Twig\Node\Expression\AssignNameExpression; use Twig\Node\Expression\Binary\AbstractBinary; +use Twig\Node\Expression\Binary\AddBinary; use Twig\Node\Expression\Binary\ConcatBinary; +use Twig\Node\Expression\Binary\SubBinary; use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\GetAttrExpression; @@ -91,6 +93,18 @@ class ExpressionParser $token = $this->parser->getCurrentToken(); } + if ( + ($expr instanceof AddBinary || $expr instanceof SubBinary) + && + ( + ($expr->getNode('left') instanceof ConcatBinary && !$expr->getNode('left')->hasExplicitParentheses()) + || + ($expr->getNode('right') instanceof ConcatBinary && !$expr->getNode('right')->hasExplicitParentheses()) + ) + ) { + trigger_deprecation('twig/twig', '3.15', \sprintf('As "+" / "-" will have a higher precedence than "~" in Twig 4.0, please add parentheses to keep the current behavior in "%s" at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $token->getLine())); + } + if (0 === $precedence) { return $this->parseConditionalExpression($expr); } diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 4a5bf233f..1dc051d8b 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -323,6 +323,7 @@ final class CoreExtension extends AbstractExtension '..' => ['precedence' => 25, 'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], '+' => ['precedence' => 30, 'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], '-' => ['precedence' => 30, 'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + // Precedence of the ~ operator will change to 27 in Twig 4.0 '~' => ['precedence' => 40, 'class' => ConcatBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], '*' => ['precedence' => 60, 'class' => MulBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], '/' => ['precedence' => 60, 'class' => DivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], diff --git a/tests/Fixtures/operators/concat_vs_add_sub.test b/tests/Fixtures/operators/concat_vs_add_sub.test new file mode 100644 index 000000000..298a3499b --- /dev/null +++ b/tests/Fixtures/operators/concat_vs_add_sub.test @@ -0,0 +1,16 @@ +--TEST-- ++/- will have a higher precedence over ~ in Twig 4.0 +--TEMPLATE-- +{{ 1 + 41 }} +{{ '42==' ~ '42' }} +{{ '42==' ~ (1 + 41) }} +{{ '42==' ~ (43 - 1) }} +{{ ('42' ~ 43) - 1 }} +--DATA-- +return [] +--EXPECT-- +42 +42==42 +42==42 +42==42 +4242 diff --git a/tests/Fixtures/operators/contat_vs_add_sub.legacy.test b/tests/Fixtures/operators/contat_vs_add_sub.legacy.test new file mode 100644 index 000000000..dc4a1ef50 --- /dev/null +++ b/tests/Fixtures/operators/contat_vs_add_sub.legacy.test @@ -0,0 +1,13 @@ +--TEST-- ++/- will have a higher precedence over ~ in Twig 4.0 +--DEPRECATION-- +Since twig/twig 3.15: As "+" / "-" will have a higher precedence than "~" in Twig 4.0, please add parentheses to keep the current behavior in "index.twig" at line 2. +Since twig/twig 3.15: As "+" / "-" will have a higher precedence than "~" in Twig 4.0, please add parentheses to keep the current behavior in "index.twig" at line 3. +--TEMPLATE-- +{{ '42' ~ 1 + 41 }} +{{ '42' ~ 43 - 1 }} +--DATA-- +return [] +--EXPECT-- +462 +4242