Deprecate using ?? without explicit parentheses

This commit is contained in:
Fabien Potencier
2024-09-30 11:51:58 +02:00
parent 6980344bc6
commit f4aacafd78
8 changed files with 73 additions and 33 deletions
+1
View File
@@ -1,5 +1,6 @@
# 3.15.0 (2024-XX-XX)
* Deprecate using `??` without explicit parentheses
* 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`)
+20 -3
View File
@@ -315,9 +315,9 @@ 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
``~``).
clarify precedence triggers a deprecation 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::
@@ -331,3 +331,20 @@ Operators
{# or #}
{{ '42' ~ (1 + 41) }} {# this is equivalent to what Twig 4.x will do without the parentheses #}
* Using ``??`` without explicit parentheses to clarify precedence triggers a
deprecation as of Twig 3.15 (in Twig 4.0, parentheses won't be needed anymore
as ``??`` will have the lowest precedence).
For example, the following expression will trigger a deprecation in Twig 3.15::
{{ 'notnull' ?? 'foo' ~ '_bar' }}
To avoid the deprecation, wrap the ``??`` expressionin parentheses to clarify
the precedence::
{{ ('notnull' ?? 'foo') ~ '_bar' }} {# this is equivalent to what Twig 3.x does without the parentheses #}
{# or #}
{{ 'notnull' ?? ('foo' ~ '_bar') }} {# this is equivalent to what Twig 4.x will do without the parentheses #}
+28 -21
View File
@@ -20,9 +20,7 @@ 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;
@@ -57,6 +55,7 @@ class ExpressionParser
/** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: self::OPERATOR_*}> */
private $binaryOperators;
private $readyNodes = [];
private array $precedenceChanges = [];
public function __construct(
private Parser $parser,
@@ -64,6 +63,20 @@ class ExpressionParser
) {
$this->unaryOperators = $env->getUnaryOperators();
$this->binaryOperators = $env->getBinaryOperators();
foreach ($this->binaryOperators as $name => $config) {
if (!isset($config['future_precedence'])) {
continue;
}
$min = min($config['future_precedence'], $config['precedence']);
$max = max($config['future_precedence'], $config['precedence']);
foreach ($this->binaryOperators as $n => $c) {
if ($c['precedence'] > $min && $c['precedence'] < $max) {
$this->precedenceChanges[$n][] = $name;
}
}
}
}
public function parseExpression($precedence = 0, $allowArrow = false)
@@ -90,10 +103,22 @@ class ExpressionParser
$expr = new $class($expr, $expr1, $token->getLine());
}
$expr->setAttribute('operator', $token->getValue());
$token = $this->parser->getCurrentToken();
}
$this->triggerPrecedenceDeprecations($expr, $token);
// Check that the all nodes that are between the 2 precedences have explicit parentheses
if ($expr->hasAttribute('operator') && isset($this->precedenceChanges[$expr->getAttribute('operator')])) {
foreach ($this->precedenceChanges[$expr->getAttribute('operator')] as $operatorName) {
foreach ($expr as $node) {
/** @var AbstractExpression $node */
if ($node->hasAttribute('operator') && $operatorName === $node->getAttribute('operator') && !$node->hasExplicitParentheses()) {
trigger_deprecation('twig/twig', '3.15', \sprintf('Add explicit parentheses around the "%s" operator to avoid behavior change in Twig 4.0 as its precedence will change in "%s" at line %d.', $operatorName, $this->parser->getStream()->getSourceContext()->getName(), $token->getLine()));
}
}
}
}
if (0 === $precedence) {
return $this->parseConditionalExpression($expr);
@@ -102,24 +127,6 @@ class ExpressionParser
return $expr;
}
private function triggerPrecedenceDeprecations(AbstractExpression $expr, Token $token): void
{
// Precedence of the ~ operator will be lower than + and - in Twig 4.0
if ($expr instanceof AddBinary || $expr instanceof SubBinary) {
/** @var AbstractExpression $left */
$left = $expr->getNode('left');
/** @var AbstractExpression $right */
$right = $expr->getNode('right');
if (
($left instanceof ConcatBinary && !$left->hasExplicitParentheses())
||
($right instanceof ConcatBinary && !$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()));
}
}
}
/**
* @return ArrowFunctionExpression|null
*/
+2 -3
View File
@@ -323,8 +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' => 40, 'future_precedence' => 27, '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],
'//' => ['precedence' => 60, 'class' => FloorDivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
@@ -332,7 +331,7 @@ final class CoreExtension extends AbstractExtension
'is' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'is not' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'**' => ['precedence' => 200, 'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
'??' => ['precedence' => 300, 'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
'??' => ['precedence' => 300, 'future_precedence' => 5, 'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
],
];
}
+1 -1
View File
@@ -598,7 +598,7 @@ class EnvironmentTest_Extension extends AbstractExtension implements GlobalsInte
{
return [
['foo_unary' => []],
['foo_binary' => []],
['foo_binary' => ['precedence' => 0]],
];
}
@@ -1,8 +1,8 @@
--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.
Since twig/twig 3.15: Add explicit parentheses around the "~" operator to avoid behavior change in Twig 4.0 as its precedence will change in "index.twig" at line 2.
Since twig/twig 3.15: Add explicit parentheses around the "~" operator to avoid behavior change in Twig 4.0 as its precedence will change in "index.twig" at line 3.
--TEMPLATE--
{{ '42' ~ 1 + 41 }}
{{ '42' ~ 43 - 1 }}
@@ -0,0 +1,16 @@
--TEST--
Twig supports the ?? operator
--DEPRECATION--
Since twig/twig 3.15: Add explicit parentheses around the "??" operator to avoid behavior change in Twig 4.0 as its precedence will change in "index.twig" at line 4.
Since twig/twig 3.15: Add explicit parentheses around the "??" operator to avoid behavior change in Twig 4.0 as its precedence will change in "index.twig" at line 5.
--TEMPLATE--
{{ nope ?? nada ?? 'OK' -}} {# no deprecation as the operators have the same precedence #}
{{ 1 + nope ?? nada ?? 2 }}
{{ 1 + nope ?? 3 + nada ?? 2 }}
--DATA--
return []
--EXPECT--
OK
3
6
+3 -3
View File
@@ -10,9 +10,9 @@ Twig supports the ?? operator
{{ foo.bar.baz.missing ?? 'OK' }}
{{ foo['bar'] ?? 'KO' }}
{{ foo['missing'] ?? 'OK' }}
{{ nope ?? nada ?? 'OK' }}
{{ 1 + nope ?? nada ?? 2 }}
{{ 1 + nope ?? 3 + nada ?? 2 }}
{{ nope ?? (nada ?? 'OK') }}
{{ 1 + (nope ?? (nada ?? 2)) }}
{{ 1 + (nope ?? 3) + (nada ?? 2) }}
--DATA--
return ['bar' => 'OK', 'foo' => ['bar' => 'OK']]
--EXPECT--