Fix Markup truthiness in boolean expressions

This commit is contained in:
Alexander Pankratov
2026-06-11 15:33:21 +02:00
parent 7f9714a13e
commit f5afaabf54
9 changed files with 62 additions and 15 deletions
+7
View File
@@ -14,9 +14,16 @@ namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
use Twig\Node\Expression\Test\TrueTest;
use Twig\Node\Node;
class AndBinary extends AbstractBinary implements ReturnBoolInterface
{
public function __construct(Node $left, Node $right, int $lineno)
{
parent::__construct(TrueTest::wrap($left), TrueTest::wrap($right), $lineno);
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('&&');
+2 -1
View File
@@ -14,6 +14,7 @@ namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\OperatorEscapeInterface;
use Twig\Node\Expression\Test\TrueTest;
use Twig\Node\Node;
final class ElvisBinary extends AbstractBinary implements OperatorEscapeInterface
@@ -26,7 +27,7 @@ final class ElvisBinary extends AbstractBinary implements OperatorEscapeInterfac
{
parent::__construct($left, $right, $lineno);
$this->setNode('test', clone $left);
$this->setNode('test', TrueTest::wrap(clone $left));
$left->setAttribute('always_defined', true);
}
+7
View File
@@ -14,9 +14,16 @@ namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
use Twig\Node\Expression\Test\TrueTest;
use Twig\Node\Node;
class OrBinary extends AbstractBinary implements ReturnBoolInterface
{
public function __construct(Node $left, Node $right, int $lineno)
{
parent::__construct(TrueTest::wrap($left), TrueTest::wrap($right), $lineno);
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('||');
+7
View File
@@ -14,9 +14,16 @@ namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
use Twig\Node\Expression\Test\TrueTest;
use Twig\Node\Node;
class XorBinary extends AbstractBinary implements ReturnBoolInterface
{
public function __construct(Node $left, Node $right, int $lineno)
{
parent::__construct(TrueTest::wrap($left), TrueTest::wrap($right), $lineno);
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('xor');
@@ -14,19 +14,13 @@ namespace Twig\Node\Expression\Ternary;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\OperatorEscapeInterface;
use Twig\Node\Expression\ReturnPrimitiveTypeInterface;
use Twig\Node\Expression\Test\TrueTest;
use Twig\TwigTest;
final class ConditionalTernary extends AbstractExpression implements OperatorEscapeInterface
{
public function __construct(AbstractExpression $test, AbstractExpression $left, AbstractExpression $right, int $lineno)
{
if (!$test instanceof ReturnPrimitiveTypeInterface) {
$test = new TrueTest($test, new TwigTest('true', null, ['always_allowed_in_sandbox' => true]), null, $test->getTemplateLine());
}
parent::__construct(['test' => $test, 'left' => $left, 'right' => $right], [], $lineno);
parent::__construct(['test' => TrueTest::wrap($test), 'left' => $left, 'right' => $right], [], $lineno);
}
public function compile(Compiler $compiler): void
+12
View File
@@ -12,7 +12,10 @@
namespace Twig\Node\Expression\Test;
use Twig\Compiler;
use Twig\Node\Expression\ReturnPrimitiveTypeInterface;
use Twig\Node\Expression\TestExpression;
use Twig\Node\Node;
use Twig\TwigTest;
/**
* Checks that an expression is true.
@@ -23,6 +26,15 @@ use Twig\Node\Expression\TestExpression;
*/
class TrueTest extends TestExpression
{
public static function wrap(Node $node): Node
{
if ($node instanceof ReturnPrimitiveTypeInterface) {
return $node;
}
return new self($node, new TwigTest('true', null, ['always_allowed_in_sandbox' => true]), null, $node->getTemplateLine());
}
public function compile(Compiler $compiler): void
{
$compiler
+9 -1
View File
@@ -13,9 +13,17 @@
namespace Twig\Node\Expression\Unary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
use Twig\Node\Expression\Test\TrueTest;
use Twig\Node\Node;
class NotUnary extends AbstractUnary
class NotUnary extends AbstractUnary implements ReturnBoolInterface
{
public function __construct(Node $node, int $lineno)
{
parent::__construct(TrueTest::wrap($node), $lineno);
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('!');
+1 -6
View File
@@ -14,9 +14,7 @@ namespace Twig\Node;
use Twig\Attribute\YieldReady;
use Twig\Compiler;
use Twig\Node\Expression\ReturnPrimitiveTypeInterface;
use Twig\Node\Expression\Test\TrueTest;
use Twig\TwigTest;
/**
* Represents an if node.
@@ -29,10 +27,7 @@ class IfNode extends Node
public function __construct(Node $tests, ?Node $else, int $lineno)
{
for ($i = 0, $count = \count($tests); $i < $count; $i += 2) {
$test = $tests->getNode((string) $i);
if (!$test instanceof ReturnPrimitiveTypeInterface) {
$tests->setNode($i, new TrueTest($test, new TwigTest('true', null, ['always_allowed_in_sandbox' => true]), null, $test->getTemplateLine()));
}
$tests->setNode($i, TrueTest::wrap($tests->getNode((string) $i)));
}
$nodes = ['tests' => $tests];
if (null !== $else) {
@@ -8,6 +8,14 @@ Twig outputs 0 nodes correctly
{% if spaces|trim %}KO{% else %}ok{% endif %}
{% set bar %} {% endset %}{{ bar|trim ? 'KO' : 'ok' }}
{% if bar|trim and bar|trim %}KO{% else %}ok{% endif %}{{- "\n" -}}
{% if bar|trim or empty|trim %}KO{% else %}ok{% endif %}{{- "\n" -}}
{% if bar|trim xor empty|trim %}KO{% else %}ok{% endif %}{{- "\n" -}}
{% if not bar|trim %}ok{% else %}KO{% endif %}{{- "\n" -}}
{{ bar|trim ?: 'ok' }}
{{ (bar|trim and bar|trim) ? 'KO' : 'ok' }}
{{ (bar|trim and bar|trim) ?: 'ok' }}
{{ (bar|trim and bar|trim) ?: (bar|trim ?: 'ok') }}
--DATA--
return ['spaces' => new Twig\Markup(' ', 'UTF-8'), 'empty' => new Twig\Markup('', 'UTF-8')]
--EXPECT--
@@ -16,3 +24,11 @@ ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok