mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-15 11:56:50 +00:00
minor #3896 Compile Elvis operator into Elvis operator ?: (GromNaN)
This PR was merged into the 3.x branch.
Discussion
----------
Compile Elvis operator into Elvis operator `?:`
When using ternary operator without "then" part, the "condition" part is evaluated twice, which is inconsistent with how PHP works.
The Twig template `A ?: B` is currently compiled as `A ? A : B` in PHP. This PR change it to `A ?: B`.
If `A` is a complex expression, it improves performance to only execute the expression once.
If `A` is has a side effect (like updating a variable), the expression being executed twice could result in a bug. ([example in PHP](https://3v4l.org/LWZLR))
Example with ``@WebProfiler`/Collector/form.html.twig` [line 9](https://github.com/symfony/symfony/blob/e6d1ed4edb5ae197ec7d25ddaf64cfa456229504/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig#L9) (see deep [diff](https://www.diffchecker.com/jOm3dE13/))
```twig
{{ collector.data.nb_errors ?: collector.data.forms|length }}
```
Previous compilation:
```php
echo twig_escape_filter($this->env, (((isset($context["error_count"]) || array_key_exists("error_count", $context) ? $context["error_count"] : (function () { throw new RuntimeError('Variable "error_count" does not exist.', 9, $this->source); })())) ? ((isset($context["error_count"]) || array_key_exists("error_count", $context) ? $context["error_count"] : (function () { throw new RuntimeError('Variable "error_count" does not exist.', 9, $this->source); })())) : (twig_get_attribute($this->env, $this->source, (isset($context["collector"]) || array_key_exists("collector", $context) ? $context["collector"] : (function () { throw new RuntimeError('Variable "collector" does not exist.', 9, $this->source); })()), "countDefines", [], "any", false, false, false, 9))), "html", null, true);
```
After this change:
```php
echo twig_escape_filter($this->env, ((isset($context["error_count"]) || array_key_exists("error_count", $context) ? $context["error_count"] : (function () { throw new RuntimeError('Variable "error_count" does not exist.', 9, $this->source); })()) ?: twig_get_attribute($this->env, $this->source, (isset($context["collector"]) || array_key_exists("collector", $context) ? $context["collector"] : (function () { throw new RuntimeError('Variable "collector" does not exist.', 9, $this->source); })()), "countDefines", [], "any", false, false, false, 9)), "html", null, true);
```
Commits
-------
fb0d7495 Compile Elvis operator with Elvis operator
This commit is contained in:
@@ -183,11 +183,14 @@ class ExpressionParser
|
||||
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
|
||||
$expr2 = $this->parseExpression();
|
||||
if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
|
||||
// Ternary operator (expr ? expr2 : expr3)
|
||||
$expr3 = $this->parseExpression();
|
||||
} else {
|
||||
// Ternary without else (expr ? expr2)
|
||||
$expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
|
||||
}
|
||||
} else {
|
||||
// Ternary without then (expr ?: expr3)
|
||||
$expr2 = $expr;
|
||||
$expr3 = $this->parseExpression();
|
||||
}
|
||||
|
||||
@@ -23,14 +23,23 @@ class ConditionalExpression extends AbstractExpression
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('((')
|
||||
->subcompile($this->getNode('expr1'))
|
||||
->raw(') ? (')
|
||||
->subcompile($this->getNode('expr2'))
|
||||
->raw(') : (')
|
||||
->subcompile($this->getNode('expr3'))
|
||||
->raw('))')
|
||||
;
|
||||
// Ternary with no then uses Elvis operator
|
||||
if ($this->getNode('expr1') === $this->getNode('expr2')) {
|
||||
$compiler
|
||||
->raw('((')
|
||||
->subcompile($this->getNode('expr1'))
|
||||
->raw(') ?: (')
|
||||
->subcompile($this->getNode('expr3'))
|
||||
->raw('))');
|
||||
} else {
|
||||
$compiler
|
||||
->raw('((')
|
||||
->subcompile($this->getNode('expr1'))
|
||||
->raw(') ? (')
|
||||
->subcompile($this->getNode('expr2'))
|
||||
->raw(') : (')
|
||||
->subcompile($this->getNode('expr3'))
|
||||
->raw('))');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user