Fix testing and expression when it evaluates to an instance of Markup

This commit is contained in:
Fabien Potencier
2025-02-21 07:23:14 +01:00
parent 9f275f6ac6
commit d72c1c6b1e
44 changed files with 214 additions and 33 deletions
+2
View File
@@ -1,5 +1,7 @@
# 3.21.0 (2025-XX-XX)
* Fix testing and expression when it evaluates to an instance of `Markup`
* Add `ReturnPrimitiveTypeInterface` (and sub-interfaces for number, boolean, string, and array)
* Add `SupportDefinedTestInterface` for expression nodes supporting the `defined` test
* Deprecate using the `|` operator in an expression with `+` or `-` without using parentheses to clarify precedence
* Deprecate operator precedence outside of the [0, 512] range
+2
View File
@@ -76,6 +76,7 @@ use Twig\Node\Expression\Test\EvenTest;
use Twig\Node\Expression\Test\NullTest;
use Twig\Node\Expression\Test\OddTest;
use Twig\Node\Expression\Test\SameasTest;
use Twig\Node\Expression\Test\TrueTest;
use Twig\Node\Expression\Unary\NegUnary;
use Twig\Node\Expression\Unary\NotUnary;
use Twig\Node\Expression\Unary\PosUnary;
@@ -318,6 +319,7 @@ final class CoreExtension extends AbstractExtension
new TwigTest('iterable', 'is_iterable'),
new TwigTest('sequence', [self::class, 'testSequence']),
new TwigTest('mapping', [self::class, 'testMapping']),
new TwigTest('true', null, ['node_class' => TrueTest::class]),
];
}
+1 -1
View File
@@ -16,7 +16,7 @@ use Twig\Node\Expression\Unary\SpreadUnary;
use Twig\Node\Expression\Unary\StringCastUnary;
use Twig\Node\Expression\Variable\ContextVariable;
class ArrayExpression extends AbstractExpression implements SupportDefinedTestInterface
class ArrayExpression extends AbstractExpression implements SupportDefinedTestInterface, ReturnArrayInterface
{
use SupportDefinedTestTrait;
+2 -1
View File
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class AddBinary extends AbstractBinary
class AddBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
+2 -1
View File
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class AndBinary extends AbstractBinary
class AndBinary extends AbstractBinary implements ReturnBoolInterface
{
public function operator(Compiler $compiler): Compiler
{
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class BitwiseAndBinary extends AbstractBinary
class BitwiseAndBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class BitwiseOrBinary extends AbstractBinary
class BitwiseOrBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class BitwiseXorBinary extends AbstractBinary
class BitwiseXorBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
+2 -1
View File
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnStringInterface;
class ConcatBinary extends AbstractBinary
class ConcatBinary extends AbstractBinary implements ReturnStringInterface
{
public function operator(Compiler $compiler): Compiler
{
+2 -1
View File
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class DivBinary extends AbstractBinary
class DivBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class EndsWithBinary extends AbstractBinary
class EndsWithBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
+2 -1
View File
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class EqualBinary extends AbstractBinary
class EqualBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class FloorDivBinary extends AbstractBinary
class FloorDivBinary extends AbstractBinary implements ReturnNumberInterface
{
public function compile(Compiler $compiler): void
{
+2 -1
View File
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class GreaterBinary extends AbstractBinary
class GreaterBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class GreaterEqualBinary extends AbstractBinary
class GreaterEqualBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class HasEveryBinary extends AbstractBinary
class HasEveryBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
+2 -1
View File
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class HasSomeBinary extends AbstractBinary
class HasSomeBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
+2 -1
View File
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class InBinary extends AbstractBinary
class InBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
+2 -1
View File
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class LessBinary extends AbstractBinary
class LessBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class LessEqualBinary extends AbstractBinary
class LessEqualBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
+2 -1
View File
@@ -13,10 +13,11 @@ namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\ReturnBoolInterface;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Node;
class MatchesBinary extends AbstractBinary
class MatchesBinary extends AbstractBinary implements ReturnBoolInterface
{
public function __construct(Node $left, Node $right, int $lineno)
{
+2 -1
View File
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class ModBinary extends AbstractBinary
class ModBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
+2 -1
View File
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class MulBinary extends AbstractBinary
class MulBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class NotEqualBinary extends AbstractBinary
class NotEqualBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
+2 -1
View File
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class NotInBinary extends AbstractBinary
class NotInBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
+2 -1
View File
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class OrBinary extends AbstractBinary
class OrBinary extends AbstractBinary implements ReturnBoolInterface
{
public function operator(Compiler $compiler): Compiler
{
+2 -1
View File
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class PowerBinary extends AbstractBinary
class PowerBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
+2 -1
View File
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnArrayInterface;
class RangeBinary extends AbstractBinary
class RangeBinary extends AbstractBinary implements ReturnArrayInterface
{
public function compile(Compiler $compiler): void
{
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class SpaceshipBinary extends AbstractBinary
class SpaceshipBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
@@ -12,8 +12,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class StartsWithBinary extends AbstractBinary
class StartsWithBinary extends AbstractBinary implements ReturnBoolInterface
{
public function compile(Compiler $compiler): void
{
+2 -1
View File
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnNumberInterface;
class SubBinary extends AbstractBinary
class SubBinary extends AbstractBinary implements ReturnNumberInterface
{
public function operator(Compiler $compiler): Compiler
{
+2 -1
View File
@@ -13,8 +13,9 @@
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\ReturnBoolInterface;
class XorBinary extends AbstractBinary
class XorBinary extends AbstractBinary implements ReturnBoolInterface
{
public function operator(Compiler $compiler): Compiler
{
+1 -1
View File
@@ -17,7 +17,7 @@ use Twig\Compiler;
/**
* @final
*/
class ConstantExpression extends AbstractExpression implements SupportDefinedTestInterface
class ConstantExpression extends AbstractExpression implements SupportDefinedTestInterface, ReturnPrimitiveTypeInterface
{
use SupportDefinedTestTrait;
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
interface ReturnArrayInterface extends ReturnPrimitiveTypeInterface
{
}
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
interface ReturnBoolInterface extends ReturnPrimitiveTypeInterface
{
}
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
interface ReturnNumberInterface extends ReturnPrimitiveTypeInterface
{
}
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
interface ReturnPrimitiveTypeInterface
{
}
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
interface ReturnStringInterface extends ReturnPrimitiveTypeInterface
{
}
@@ -14,11 +14,18 @@ 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, $test->getTemplateLine());
}
parent::__construct(['test' => $test, 'left' => $left, 'right' => $right], [], $lineno);
}
+1 -1
View File
@@ -15,7 +15,7 @@ use Twig\Compiler;
use Twig\Node\Expression\TestExpression;
/**
* Checks that a variable is null.
* Checks that an expression is null.
*
* {{ var is none }}
*
+34
View File
@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Test;
use Twig\Compiler;
use Twig\Node\Expression\TestExpression;
/**
* Checks that an expression is true.
*
* {{ var is true }}
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TrueTest extends TestExpression
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('(($tmp = ')
->subcompile($this->getNode('node'))
->raw(') && $tmp instanceof Markup ? (string) $tmp : $tmp)')
;
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ use Twig\Node\NameDeprecation;
use Twig\Node\Node;
use Twig\TwigTest;
class TestExpression extends CallExpression
class TestExpression extends CallExpression implements ReturnBoolInterface
{
#[FirstClassTwigCallableReady]
/**
+9
View File
@@ -14,6 +14,9 @@ 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.
@@ -25,6 +28,12 @@ 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, $test->getTemplateLine()));
}
}
$nodes = ['tests' => $tests];
if (null !== $else) {
$nodes['else'] = $else;
@@ -0,0 +1,18 @@
--TEST--
Twig outputs 0 nodes correctly
--TEMPLATE--
{{ empty|trim ? 'KO' : 'ok' }}
{{ spaces|trim ? 'KO' : 'ok' }}
{% if empty %}KO{% else %}ok{% endif %}
{% if spaces|trim %}KO{% else %}ok{% endif %}
{% set bar %} {% endset %}{{ bar|trim ? 'KO' : 'ok' }}
--DATA--
return ['spaces' => new Twig\Markup(' ', 'UTF-8'), 'empty' => new Twig\Markup('', 'UTF-8')]
--EXPECT--
ok
ok
ok
ok
ok