diff --git a/bin/generate_operators_precedence.php b/bin/generate_operators_precedence.php index 101adb1a7..72add5ea6 100644 --- a/bin/generate_operators_precedence.php +++ b/bin/generate_operators_precedence.php @@ -21,19 +21,24 @@ require_once \dirname(__DIR__).'/vendor/autoload.php'; $output = fopen(\dirname(__DIR__).'/doc/operators_precedence.rst', 'w'); $twig = new Environment(new ArrayLoader([])); +$descriptionLength = 11; $expressionParsers = []; foreach ($twig->getExpressionParsers() as $expressionParser) { $expressionParsers[] = $expressionParser; + $descriptionLength = max($descriptionLength, $expressionParser instanceof ExpressionParserDescriptionInterface ? strlen($expressionParser->getDescription()) : ''); } -fwrite($output, "\n=========== ================ ======= ============= ===========\n"); -fwrite($output, "Precedence Operator Type Associativity Description\n"); -fwrite($output, '=========== ================ ======= ============= ==========='); +fwrite($output, "\n+------------+------------------+---------+---------------+".str_repeat('-', $descriptionLength + 2)."+\n"); +fwrite($output, "| Precedence | Operator | Type | Associativity | Description".str_repeat(' ', $descriptionLength - 11)." |\n"); +fwrite($output, '+============+==================+=========+===============+'.str_repeat('=', $descriptionLength + 2).'+'); usort($expressionParsers, fn ($a, $b) => $b->getPrecedence() <=> $a->getPrecedence()); $previous = null; foreach ($expressionParsers as $expressionParser) { + if (null !== $previous) { + fwrite($output, "\n+------------+------------------+---------+---------------+".str_repeat('-', $descriptionLength + 2).'+'); + } $precedence = $expressionParser->getPrecedence(); $previousPrecedence = $previous ? $previous->getPrecedence() : \PHP_INT_MAX; $associativity = $expressionParser instanceof InfixExpressionParserInterface ? (InfixAssociativity::Left === $expressionParser->getAssociativity() ? 'Left' : 'Right') : 'n/a'; @@ -41,7 +46,7 @@ foreach ($expressionParsers as $expressionParser) { if ($previousPrecedence !== $precedence) { $previous = null; } - fwrite($output, rtrim(\sprintf("\n%-11s %-16s %-7s %-13s %s\n", + fwrite($output, rtrim(\sprintf("\n| %-10s | %-16s | %-7s | %-13s | %-{$descriptionLength}s |\n", (!$previous || $previousPrecedence !== $precedence ? $precedence : '').($expressionParser->getPrecedenceChange() ? ' => '.$expressionParser->getPrecedenceChange()->getNewPrecedence() : ''), '``'.$expressionParser->getName().'``', !$previous || ExpressionParserType::getType($previous) !== ExpressionParserType::getType($expressionParser) ? ExpressionParserType::getType($expressionParser)->value : '', @@ -50,7 +55,7 @@ foreach ($expressionParsers as $expressionParser) { ))); $previous = $expressionParser; } -fwrite($output, "\n=========== ================ ======= ============= ===========\n"); +fwrite($output, "\n+------------+------------------+---------+---------------+".str_repeat('-', $descriptionLength + 2)."+\n"); fwrite($output, "\nWhen a precedence will change in the next major version, the new precedence is indicated by the arrow ``=>``.\n"); fclose($output); diff --git a/doc/advanced.rst b/doc/advanced.rst index 9ec69ae3f..0414b1048 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -749,7 +749,7 @@ responsible for parsing the tag and compiling it to PHP. Operators ~~~~~~~~~ -The ``getOperators()`` methods lets you add new operators. To implement a new +The ``getOperators()`` method lets you add new operators. To implement a new one, have a look at the default operators provided by ``Twig\Extension\CoreExtension``. diff --git a/doc/operators_precedence.rst b/doc/operators_precedence.rst index 9bd05aee0..1da53c679 100644 --- a/doc/operators_precedence.rst +++ b/doc/operators_precedence.rst @@ -1,53 +1,98 @@ -=========== ================ ======= ============= =========== -Precedence Operator Type Associativity Description -=========== ================ ======= ============= =========== -512 ``...`` prefix n/a Spread operator - ``(`` infix Left Twig function call - ``.`` Get an attribute on a variable - ``[`` Array access -500 ``-`` prefix n/a - ``+`` -300 ``|`` infix Left Twig filter call -250 ``=>`` infix Left Arrow function (x => expr) -200 ``**`` infix Right Exponentiation operator -100 ``is`` infix Left Twig tests - ``is not`` Twig tests -70 ``not`` prefix n/a -60 ``*`` infix Left - ``/`` - ``//`` Floor division - ``%`` -30 ``+`` infix Left - ``-`` -27 ``~`` infix Left -25 ``..`` infix Left -20 ``==`` infix Left - ``!=`` - ``<=>`` - ``<`` - ``>`` - ``>=`` - ``<=`` - ``not in`` - ``in`` - ``matches`` - ``starts with`` - ``ends with`` - ``has some`` - ``has every`` -18 ``b-and`` infix Left -17 ``b-xor`` infix Left -16 ``b-or`` infix Left -15 ``and`` infix Left -12 ``xor`` infix Left -10 ``or`` infix Left -5 ``?:`` infix Right Elvis operator (a ?: b) - ``?:`` Elvis operator (a ?: b) - ``??`` Null coalescing operator (a ?? b) -0 ``(`` prefix n/a Explicit group expression (a) - ``literal`` A literal value (boolean, string, number, sequence, mapping, ...) - ``?`` infix Left Conditional operator (a ? b : c) -=========== ================ ======= ============= =========== ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| Precedence | Operator | Type | Associativity | Description | ++============+==================+=========+===============+===================================================================+ +| 512 | ``...`` | prefix | n/a | Spread operator | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``(`` | infix | Left | Twig function call | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``.`` | | | Get an attribute on a variable | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``[`` | | | Array access | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 500 | ``-`` | prefix | n/a | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``+`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 300 | ``|`` | infix | Left | Twig filter call | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 250 | ``=>`` | infix | Left | Arrow function (x => expr) | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 200 | ``**`` | infix | Right | Exponentiation operator | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 100 | ``is`` | infix | Left | Twig tests | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``is not`` | | | Twig tests | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 70 | ``not`` | prefix | n/a | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 60 | ``*`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``/`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``//`` | | | Floor division | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``%`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 30 | ``+`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``-`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 27 | ``~`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 25 | ``..`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 20 | ``==`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``!=`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``<=>`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``<`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``>`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``>=`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``<=`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``not in`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``in`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``matches`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``starts with`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``ends with`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``has some`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``has every`` | | | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 18 | ``b-and`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 17 | ``b-xor`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 16 | ``b-or`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 15 | ``and`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 12 | ``xor`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 10 | ``or`` | infix | Left | | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 5 | ``?:`` | infix | Right | Elvis operator (a ?: b) | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``?:`` | | | Elvis operator (a ?: b) | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``??`` | | | Null coalescing operator (a ?? b) | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| 0 | ``(`` | prefix | n/a | Explicit group expression (a) | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``literal`` | | | A literal value (boolean, string, number, sequence, mapping, ...) | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ +| | ``?`` | infix | Left | Conditional operator (a ? b : c) | ++------------+------------------+---------+---------------+-------------------------------------------------------------------+ When a precedence will change in the next major version, the new precedence is indicated by the arrow ``=>``. diff --git a/src/Error/Error.php b/src/Error/Error.php index bfb5f6e44..2f88f02a6 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -39,10 +39,8 @@ use Twig\Template; class Error extends \Exception { private int $lineno; - private ?string $name; private string $rawMessage; - private ?string $sourcePath = null; - private ?string $sourceCode = null; + private ?Source $source; /** * Constructor. @@ -57,16 +55,8 @@ class Error extends \Exception { parent::__construct('', 0, $previous); - if (null === $source) { - $name = null; - } else { - $name = $source->getName(); - $this->sourceCode = $source->getCode(); - $this->sourcePath = $source->getPath(); - } - $this->lineno = $lineno; - $this->name = $name; + $this->source = $source; $this->rawMessage = $message; $this->updateRepr(); } @@ -84,25 +74,17 @@ class Error extends \Exception public function setTemplateLine(int $lineno): void { $this->lineno = $lineno; - $this->updateRepr(); } public function getSourceContext(): ?Source { - return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null; + return $this->source; } public function setSourceContext(?Source $source = null): void { - if (null === $source) { - $this->sourceCode = $this->name = $this->sourcePath = null; - } else { - $this->sourceCode = $source->getCode(); - $this->name = $source->getName(); - $this->sourcePath = $source->getPath(); - } - + $this->source = $source; $this->updateRepr(); } @@ -122,8 +104,8 @@ class Error extends \Exception { $this->message = $this->rawMessage; - if ($this->sourcePath && $this->lineno > 0) { - $this->file = $this->sourcePath; + if ($this->source && $this->source->getPath() && $this->lineno > 0) { + $this->file = $this->source->getPath(); $this->line = $this->lineno; return; @@ -141,8 +123,8 @@ class Error extends \Exception $questionMark = true; } - if ($this->name) { - $this->message .= \sprintf(' in "%s"', $this->name); + if ($this->source && $this->source->getName()) { + $this->message .= \sprintf(' in %s', $this->source->getName()); } if ($this->lineno && $this->lineno >= 0) { @@ -160,34 +142,25 @@ class Error extends \Exception private function guessTemplateInfo(): void { + // $this->source is never null here (see guess() usage in Template) + $template = null; $templateClass = null; - $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT); foreach ($backtrace as $trace) { if (isset($trace['object']) && $trace['object'] instanceof Template) { $currentClass = $trace['object']::class; $isEmbedContainer = null === $templateClass ? false : str_starts_with($templateClass, $currentClass); - if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { + if ($this->source->getName() === $trace['object']->getTemplateName() && !$isEmbedContainer) { $template = $trace['object']; $templateClass = $trace['object']::class; } } } - // update template name - if (null !== $template && null === $this->name) { - $this->name = $template->getTemplateName(); - } - - // update template path if any - if (null !== $template && null === $this->sourcePath) { - $src = $template->getSourceContext(); - $this->sourceCode = $src->getCode(); - $this->sourcePath = $src->getPath(); - } - - if (null === $template || $this->lineno > -1) { + if ($template) { + $this->source = $template->getSourceContext(); + } elseif ($this->lineno > -1) { return; } diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 34318548f..e6d80ed4d 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -77,6 +77,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; @@ -320,6 +321,7 @@ final class CoreExtension extends AbstractExtension new TwigTest('iterable', 'is_iterable'), new TwigTest('sequence', self::testSequence(...)), new TwigTest('mapping', self::testMapping(...)), + new TwigTest('true', null, ['node_class' => TrueTest::class]), ]; } diff --git a/src/Node/Expression/ArrayExpression.php b/src/Node/Expression/ArrayExpression.php index e1b0bd21a..515107424 100644 --- a/src/Node/Expression/ArrayExpression.php +++ b/src/Node/Expression/ArrayExpression.php @@ -17,8 +17,10 @@ use Twig\Node\Expression\Unary\StringCastUnary; use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Expression\Variable\LocalVariable; -class ArrayExpression extends AbstractExpression +class ArrayExpression extends AbstractExpression implements SupportDefinedTestInterface, ReturnArrayInterface { + use SupportDefinedTestTrait; + private int $index; public function __construct(array $elements, int $lineno) @@ -70,6 +72,12 @@ class ArrayExpression extends AbstractExpression public function compile(Compiler $compiler): void { + if ($this->definedTest) { + $compiler->repr(true); + + return; + } + $compiler->raw('['); $first = true; $nextIndex = 0; diff --git a/src/Node/Expression/Binary/AddBinary.php b/src/Node/Expression/Binary/AddBinary.php index ee4307e33..42377aea0 100644 --- a/src/Node/Expression/Binary/AddBinary.php +++ b/src/Node/Expression/Binary/AddBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/AndBinary.php b/src/Node/Expression/Binary/AndBinary.php index 5f2380da5..454ea70e5 100644 --- a/src/Node/Expression/Binary/AndBinary.php +++ b/src/Node/Expression/Binary/AndBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/BitwiseAndBinary.php b/src/Node/Expression/Binary/BitwiseAndBinary.php index db7d6d612..1c26f9893 100644 --- a/src/Node/Expression/Binary/BitwiseAndBinary.php +++ b/src/Node/Expression/Binary/BitwiseAndBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/BitwiseOrBinary.php b/src/Node/Expression/Binary/BitwiseOrBinary.php index ce803dd90..ec17e2280 100644 --- a/src/Node/Expression/Binary/BitwiseOrBinary.php +++ b/src/Node/Expression/Binary/BitwiseOrBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/BitwiseXorBinary.php b/src/Node/Expression/Binary/BitwiseXorBinary.php index 5c2978501..e6432a7ae 100644 --- a/src/Node/Expression/Binary/BitwiseXorBinary.php +++ b/src/Node/Expression/Binary/BitwiseXorBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/ConcatBinary.php b/src/Node/Expression/Binary/ConcatBinary.php index f825ab874..75ee65473 100644 --- a/src/Node/Expression/Binary/ConcatBinary.php +++ b/src/Node/Expression/Binary/ConcatBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/DivBinary.php b/src/Node/Expression/Binary/DivBinary.php index e3817d1cd..11c061e18 100644 --- a/src/Node/Expression/Binary/DivBinary.php +++ b/src/Node/Expression/Binary/DivBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/EndsWithBinary.php b/src/Node/Expression/Binary/EndsWithBinary.php index a73a5608d..e689d668a 100644 --- a/src/Node/Expression/Binary/EndsWithBinary.php +++ b/src/Node/Expression/Binary/EndsWithBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/EqualBinary.php b/src/Node/Expression/Binary/EqualBinary.php index e82e7b84a..594ea6086 100644 --- a/src/Node/Expression/Binary/EqualBinary.php +++ b/src/Node/Expression/Binary/EqualBinary.php @@ -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 operator(Compiler $compiler): Compiler { diff --git a/src/Node/Expression/Binary/FloorDivBinary.php b/src/Node/Expression/Binary/FloorDivBinary.php index d7e7980ef..a60ab3b21 100644 --- a/src/Node/Expression/Binary/FloorDivBinary.php +++ b/src/Node/Expression/Binary/FloorDivBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/GreaterBinary.php b/src/Node/Expression/Binary/GreaterBinary.php index 5c9581887..5730c5e7a 100644 --- a/src/Node/Expression/Binary/GreaterBinary.php +++ b/src/Node/Expression/Binary/GreaterBinary.php @@ -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 operator(Compiler $compiler): Compiler { diff --git a/src/Node/Expression/Binary/GreaterEqualBinary.php b/src/Node/Expression/Binary/GreaterEqualBinary.php index e5b44c677..94952ce90 100644 --- a/src/Node/Expression/Binary/GreaterEqualBinary.php +++ b/src/Node/Expression/Binary/GreaterEqualBinary.php @@ -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 operator(Compiler $compiler): Compiler { diff --git a/src/Node/Expression/Binary/HasEveryBinary.php b/src/Node/Expression/Binary/HasEveryBinary.php index 1e818b8ab..725057c07 100644 --- a/src/Node/Expression/Binary/HasEveryBinary.php +++ b/src/Node/Expression/Binary/HasEveryBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/HasSomeBinary.php b/src/Node/Expression/Binary/HasSomeBinary.php index 4b1d190eb..6c333d1c8 100644 --- a/src/Node/Expression/Binary/HasSomeBinary.php +++ b/src/Node/Expression/Binary/HasSomeBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/InBinary.php b/src/Node/Expression/Binary/InBinary.php index 68a98fe15..31a21e7d6 100644 --- a/src/Node/Expression/Binary/InBinary.php +++ b/src/Node/Expression/Binary/InBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/LessBinary.php b/src/Node/Expression/Binary/LessBinary.php index 3977b91ec..18ddf9604 100644 --- a/src/Node/Expression/Binary/LessBinary.php +++ b/src/Node/Expression/Binary/LessBinary.php @@ -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 operator(Compiler $compiler): Compiler { diff --git a/src/Node/Expression/Binary/LessEqualBinary.php b/src/Node/Expression/Binary/LessEqualBinary.php index 32107ddbd..db6cb0179 100644 --- a/src/Node/Expression/Binary/LessEqualBinary.php +++ b/src/Node/Expression/Binary/LessEqualBinary.php @@ -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 operator(Compiler $compiler): Compiler { diff --git a/src/Node/Expression/Binary/MatchesBinary.php b/src/Node/Expression/Binary/MatchesBinary.php index 0a523c216..32e8d34e4 100644 --- a/src/Node/Expression/Binary/MatchesBinary.php +++ b/src/Node/Expression/Binary/MatchesBinary.php @@ -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) { diff --git a/src/Node/Expression/Binary/ModBinary.php b/src/Node/Expression/Binary/ModBinary.php index 271b45cac..aef48f3d0 100644 --- a/src/Node/Expression/Binary/ModBinary.php +++ b/src/Node/Expression/Binary/ModBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/MulBinary.php b/src/Node/Expression/Binary/MulBinary.php index 6d4c1e0b6..beb881ae3 100644 --- a/src/Node/Expression/Binary/MulBinary.php +++ b/src/Node/Expression/Binary/MulBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/NotEqualBinary.php b/src/Node/Expression/Binary/NotEqualBinary.php index 7720ebf93..763b8ddec 100644 --- a/src/Node/Expression/Binary/NotEqualBinary.php +++ b/src/Node/Expression/Binary/NotEqualBinary.php @@ -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 operator(Compiler $compiler): Compiler { diff --git a/src/Node/Expression/Binary/NotInBinary.php b/src/Node/Expression/Binary/NotInBinary.php index 80c8755d8..9fd27311f 100644 --- a/src/Node/Expression/Binary/NotInBinary.php +++ b/src/Node/Expression/Binary/NotInBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/OrBinary.php b/src/Node/Expression/Binary/OrBinary.php index 21f87c91b..82dcb7e95 100644 --- a/src/Node/Expression/Binary/OrBinary.php +++ b/src/Node/Expression/Binary/OrBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/PowerBinary.php b/src/Node/Expression/Binary/PowerBinary.php index c9f4c6697..5325e8eb0 100644 --- a/src/Node/Expression/Binary/PowerBinary.php +++ b/src/Node/Expression/Binary/PowerBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/RangeBinary.php b/src/Node/Expression/Binary/RangeBinary.php index 55982c819..f318d8e55 100644 --- a/src/Node/Expression/Binary/RangeBinary.php +++ b/src/Node/Expression/Binary/RangeBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/SpaceshipBinary.php b/src/Node/Expression/Binary/SpaceshipBinary.php index ae5a4a493..c0a28b0a8 100644 --- a/src/Node/Expression/Binary/SpaceshipBinary.php +++ b/src/Node/Expression/Binary/SpaceshipBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/StartsWithBinary.php b/src/Node/Expression/Binary/StartsWithBinary.php index 4519f30d9..ef2fc9502 100644 --- a/src/Node/Expression/Binary/StartsWithBinary.php +++ b/src/Node/Expression/Binary/StartsWithBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/SubBinary.php b/src/Node/Expression/Binary/SubBinary.php index eeb87faca..10663f5c1 100644 --- a/src/Node/Expression/Binary/SubBinary.php +++ b/src/Node/Expression/Binary/SubBinary.php @@ -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 { diff --git a/src/Node/Expression/Binary/XorBinary.php b/src/Node/Expression/Binary/XorBinary.php index d8ccd7853..6f412d22f 100644 --- a/src/Node/Expression/Binary/XorBinary.php +++ b/src/Node/Expression/Binary/XorBinary.php @@ -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 { diff --git a/src/Node/Expression/BlockReferenceExpression.php b/src/Node/Expression/BlockReferenceExpression.php index 0b50c0ce9..8ba73db04 100644 --- a/src/Node/Expression/BlockReferenceExpression.php +++ b/src/Node/Expression/BlockReferenceExpression.php @@ -20,8 +20,11 @@ use Twig\Node\Node; * * @author Fabien Potencier */ -class BlockReferenceExpression extends AbstractExpression +class BlockReferenceExpression extends AbstractExpression implements SupportDefinedTestInterface { + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + public function __construct(AbstractExpression $name, ?Node $template, int $lineno) { $nodes = ['name' => $name]; @@ -29,12 +32,12 @@ class BlockReferenceExpression extends AbstractExpression $nodes['template'] = $template; } - parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno); + parent::__construct($nodes, ['output' => false], $lineno); } public function compile(Compiler $compiler): void { - if ($this->getAttribute('is_defined_test')) { + if ($this->definedTest) { $this->compileTemplateCall($compiler, 'hasBlock'); } else { if ($this->getAttribute('output')) { diff --git a/src/Node/Expression/ConstantExpression.php b/src/Node/Expression/ConstantExpression.php index 3f7112c9d..48e715ba5 100644 --- a/src/Node/Expression/ConstantExpression.php +++ b/src/Node/Expression/ConstantExpression.php @@ -14,8 +14,10 @@ namespace Twig\Node\Expression; use Twig\Compiler; -final class ConstantExpression extends AbstractExpression +final class ConstantExpression extends AbstractExpression implements SupportDefinedTestInterface, ReturnPrimitiveTypeInterface { + use SupportDefinedTestTrait; + public function __construct($value, int $lineno) { parent::__construct([], ['value' => $value], $lineno); @@ -23,6 +25,6 @@ final class ConstantExpression extends AbstractExpression public function compile(Compiler $compiler): void { - $compiler->repr($this->getAttribute('value')); + $compiler->repr($this->definedTest ? true : $this->getAttribute('value')); } } diff --git a/src/Node/Expression/FunctionExpression.php b/src/Node/Expression/FunctionExpression.php index 0e133cb84..c626691ca 100644 --- a/src/Node/Expression/FunctionExpression.php +++ b/src/Node/Expression/FunctionExpression.php @@ -16,17 +16,27 @@ use Twig\Compiler; use Twig\Node\Node; use Twig\TwigFunction; -class FunctionExpression extends CallExpression +class FunctionExpression extends CallExpression implements SupportDefinedTestInterface { + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + #[FirstClassTwigCallableReady] public function __construct(TwigFunction $function, Node $arguments, int $lineno) { - parent::__construct(['arguments' => $arguments], ['name' => $function->getName(), 'type' => 'function', 'twig_callable' => $function, 'is_defined_test' => false], $lineno); + parent::__construct(['arguments' => $arguments], ['name' => $function->getName(), 'type' => 'function', 'twig_callable' => $function], $lineno); + } + + public function enableDefinedTest(): void + { + if ('constant' === $this->getAttribute('name')) { + $this->definedTest = true; + } } public function compile(Compiler $compiler): void { - if ('constant' === $this->getAttribute('name') && $this->getAttribute('is_defined_test')) { + if ('constant' === $this->getAttribute('name') && $this->definedTest) { $this->getNode('arguments')->setNode('checkDefined', new ConstantExpression(true, $this->getTemplateLine())); } diff --git a/src/Node/Expression/GetAttrExpression.php b/src/Node/Expression/GetAttrExpression.php index 7aa7af4a5..e57d91b56 100644 --- a/src/Node/Expression/GetAttrExpression.php +++ b/src/Node/Expression/GetAttrExpression.php @@ -17,8 +17,11 @@ use Twig\Extension\SandboxExtension; use Twig\Node\Expression\Variable\ContextVariable; use Twig\Template; -class GetAttrExpression extends AbstractExpression +class GetAttrExpression extends AbstractExpression implements SupportDefinedTestInterface { + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + public function __construct(AbstractExpression $node, AbstractExpression $attribute, ArrayExpression|ContextVariable|null $arguments, string $type, int $lineno) { $nodes = ['node' => $node, 'attribute' => $attribute]; @@ -26,7 +29,13 @@ class GetAttrExpression extends AbstractExpression $nodes['arguments'] = $arguments; } - parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno); + parent::__construct($nodes, ['type' => $type, 'ignore_strict_check' => false, 'optimizable' => true], $lineno); + } + + public function enableDefinedTest(): void + { + $this->definedTest = true; + $this->changeIgnoreStrictCheck($this); } public function compile(Compiler $compiler): void @@ -38,7 +47,7 @@ class GetAttrExpression extends AbstractExpression if ( $this->getAttribute('optimizable') && (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check')) - && !$this->getAttribute('is_defined_test') + && !$this->definedTest && Template::ARRAY_CALL === $this->getAttribute('type') ) { $var = '$'.$compiler->getVarName(); @@ -97,7 +106,7 @@ class GetAttrExpression extends AbstractExpression $compiler->raw(', type: ')->repr($type); } - if ($this->getAttribute('is_defined_test')) { + if ($this->definedTest) { $compiler->raw(', isDefinedTest: true'); } @@ -118,4 +127,14 @@ class GetAttrExpression extends AbstractExpression $compiler->raw(')'); } } + + private function changeIgnoreStrictCheck(GetAttrExpression $node): void + { + $node->setAttribute('optimizable', false); + $node->setAttribute('ignore_strict_check', true); + + if ($node->getNode('node') instanceof GetAttrExpression) { + $this->changeIgnoreStrictCheck($node->getNode('node')); + } + } } diff --git a/src/Node/Expression/MacroReferenceExpression.php b/src/Node/Expression/MacroReferenceExpression.php index abe99aa35..fd7f1e733 100644 --- a/src/Node/Expression/MacroReferenceExpression.php +++ b/src/Node/Expression/MacroReferenceExpression.php @@ -19,16 +19,19 @@ use Twig\Node\Expression\Variable\TemplateVariable; * * @author Fabien Potencier */ -class MacroReferenceExpression extends AbstractExpression +class MacroReferenceExpression extends AbstractExpression implements SupportDefinedTestInterface { + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + public function __construct(TemplateVariable $template, string $name, AbstractExpression $arguments, int $lineno) { - parent::__construct(['template' => $template, 'arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno); + parent::__construct(['template' => $template, 'arguments' => $arguments], ['name' => $name], $lineno); } public function compile(Compiler $compiler): void { - if ($this->getAttribute('is_defined_test')) { + if ($this->definedTest) { $compiler ->subcompile($this->getNode('template')) ->raw('->hasMacro(') diff --git a/src/Node/Expression/ReturnArrayInterface.php b/src/Node/Expression/ReturnArrayInterface.php new file mode 100644 index 000000000..a74864b5d --- /dev/null +++ b/src/Node/Expression/ReturnArrayInterface.php @@ -0,0 +1,16 @@ + + */ +trait SupportDefinedTestDeprecationTrait +{ + public function getAttribute($name, $default = null) + { + if ('is_defined_test' === $name) { + trigger_deprecation('twig/twig', '3.21', 'The "is_defined_test" attribute is deprecated, call "isDefinedTestEnabled()" instead.'); + + return $this->isDefinedTestEnabled(); + } + + return parent::getAttribute($name, $default); + } + + public function setAttribute(string $name, $value): void + { + if ('is_defined_test' === $name) { + trigger_deprecation('twig/twig', '3.21', 'The "is_defined_test" attribute is deprecated, call "enableDefinedTest()" instead.'); + + $this->definedTest = (bool) $value; + } else { + parent::setAttribute($name, $value); + } + } +} diff --git a/src/Node/Expression/SupportDefinedTestInterface.php b/src/Node/Expression/SupportDefinedTestInterface.php new file mode 100644 index 000000000..450c691c5 --- /dev/null +++ b/src/Node/Expression/SupportDefinedTestInterface.php @@ -0,0 +1,24 @@ + + */ +interface SupportDefinedTestInterface +{ + public function enableDefinedTest(): void; + + public function isDefinedTestEnabled(): bool; +} diff --git a/src/Node/Expression/SupportDefinedTestTrait.php b/src/Node/Expression/SupportDefinedTestTrait.php new file mode 100644 index 000000000..4cf1a58d5 --- /dev/null +++ b/src/Node/Expression/SupportDefinedTestTrait.php @@ -0,0 +1,27 @@ +definedTest = true; + } + + public function isDefinedTestEnabled(): bool + { + return $this->definedTest; + } +} diff --git a/src/Node/Expression/Ternary/ConditionalTernary.php b/src/Node/Expression/Ternary/ConditionalTernary.php index 627da7a43..f7cd78c5c 100644 --- a/src/Node/Expression/Ternary/ConditionalTernary.php +++ b/src/Node/Expression/Ternary/ConditionalTernary.php @@ -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); } diff --git a/src/Node/Expression/Test/DefinedTest.php b/src/Node/Expression/Test/DefinedTest.php index 57b1532a7..562ede8f9 100644 --- a/src/Node/Expression/Test/DefinedTest.php +++ b/src/Node/Expression/Test/DefinedTest.php @@ -15,14 +15,8 @@ use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; use Twig\Error\SyntaxError; use Twig\Node\Expression\AbstractExpression; -use Twig\Node\Expression\ArrayExpression; -use Twig\Node\Expression\BlockReferenceExpression; -use Twig\Node\Expression\ConstantExpression; -use Twig\Node\Expression\FunctionExpression; -use Twig\Node\Expression\GetAttrExpression; -use Twig\Node\Expression\MacroReferenceExpression; +use Twig\Node\Expression\SupportDefinedTestInterface; use Twig\Node\Expression\TestExpression; -use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Node; use Twig\TwigTest; @@ -41,36 +35,15 @@ class DefinedTest extends TestExpression #[FirstClassTwigCallableReady] public function __construct(AbstractExpression $node, TwigTest $name, ?Node $arguments, int $lineno) { - if ($node instanceof ContextVariable) { - $node->setAttribute('is_defined_test', true); - } elseif ($node instanceof GetAttrExpression) { - $node->setAttribute('is_defined_test', true); - $this->changeIgnoreStrictCheck($node); - } elseif ($node instanceof BlockReferenceExpression) { - $node->setAttribute('is_defined_test', true); - } elseif ($node instanceof MacroReferenceExpression) { - $node->setAttribute('is_defined_test', true); - } elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) { - $node->setAttribute('is_defined_test', true); - } elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) { - $node = new ConstantExpression(true, $node->getTemplateLine()); - } else { + if (!$node instanceof SupportDefinedTestInterface) { throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); } + $node->enableDefinedTest(); + parent::__construct($node, $name, $arguments, $lineno); } - private function changeIgnoreStrictCheck(GetAttrExpression $node): void - { - $node->setAttribute('optimizable', false); - $node->setAttribute('ignore_strict_check', true); - - if ($node->getNode('node') instanceof GetAttrExpression) { - $this->changeIgnoreStrictCheck($node->getNode('node')); - } - } - public function compile(Compiler $compiler): void { $compiler->subcompile($this->getNode('node')); diff --git a/src/Node/Expression/Test/NullTest.php b/src/Node/Expression/Test/NullTest.php index 45b54ae37..be5d38891 100644 --- a/src/Node/Expression/Test/NullTest.php +++ b/src/Node/Expression/Test/NullTest.php @@ -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 }} * diff --git a/src/Node/Expression/Test/TrueTest.php b/src/Node/Expression/Test/TrueTest.php new file mode 100644 index 000000000..22186a689 --- /dev/null +++ b/src/Node/Expression/Test/TrueTest.php @@ -0,0 +1,34 @@ + + */ +class TrueTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(($tmp = ') + ->subcompile($this->getNode('node')) + ->raw(') && $tmp instanceof Markup ? (string) $tmp : $tmp)') + ; + } +} diff --git a/src/Node/Expression/TestExpression.php b/src/Node/Expression/TestExpression.php index 83745121a..3bbea37db 100644 --- a/src/Node/Expression/TestExpression.php +++ b/src/Node/Expression/TestExpression.php @@ -16,7 +16,7 @@ use Twig\Node\EmptyNode; use Twig\Node\Node; use Twig\TwigTest; -class TestExpression extends CallExpression +class TestExpression extends CallExpression implements ReturnBoolInterface { #[FirstClassTwigCallableReady] public function __construct(AbstractExpression $node, TwigTest $test, ?Node $arguments, int $lineno) diff --git a/src/Node/Expression/Variable/ContextVariable.php b/src/Node/Expression/Variable/ContextVariable.php index 8259897d6..0ec88902a 100644 --- a/src/Node/Expression/Variable/ContextVariable.php +++ b/src/Node/Expression/Variable/ContextVariable.php @@ -13,9 +13,14 @@ namespace Twig\Node\Expression\Variable; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\SupportDefinedTestDeprecationTrait; +use Twig\Node\Expression\SupportDefinedTestTrait; class ContextVariable extends AbstractExpression { + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + private array $specialVars = [ '_self' => '$this->getTemplateName()', '_context' => '$context', @@ -24,7 +29,7 @@ class ContextVariable extends AbstractExpression public function __construct(string $name, int $lineno) { - parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); + parent::__construct([], ['name' => $name, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); } public function compile(Compiler $compiler): void @@ -33,7 +38,7 @@ class ContextVariable extends AbstractExpression $compiler->addDebugInfo($this); - if ($this->getAttribute('is_defined_test')) { + if ($this->definedTest) { if (isset($this->specialVars[$name]) || $this->getAttribute('always_defined')) { $compiler->repr(true); } else { diff --git a/src/Node/IfNode.php b/src/Node/IfNode.php index 2af48fa81..2c0e2a8e9 100644 --- a/src/Node/IfNode.php +++ b/src/Node/IfNode.php @@ -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; diff --git a/tests/Fixtures/regression/markup_test.test b/tests/Fixtures/regression/markup_test.test new file mode 100644 index 000000000..e03c5291b --- /dev/null +++ b/tests/Fixtures/regression/markup_test.test @@ -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 diff --git a/tests/Node/Expression/Variable/ContextVariableTest.php b/tests/Node/Expression/Variable/ContextVariableTest.php index 911c42bc0..68d4b17d1 100644 --- a/tests/Node/Expression/Variable/ContextVariableTest.php +++ b/tests/Node/Expression/Variable/ContextVariableTest.php @@ -32,7 +32,7 @@ class ContextVariableTest extends NodeTestCase $node = new ContextVariable($special, 1); yield $special => [$node, "// line 1\n$compiled"]; $node = new ContextVariable($special, 1); - $node->setAttribute('is_defined_test', true); + $node->enableDefinedTest(); yield $special.'_defined_test' => [$node, "// line 1\ntrue"]; } @@ -59,13 +59,13 @@ class ContextVariableTest extends NodeTestCase // is defined test $node = new ContextVariable('foo', 1); - $node->setAttribute('is_defined_test', true); + $node->enableDefinedTest(); yield 'is_defined_test_strict' => [$node, "// line 1\narray_key_exists(\"foo\", \$context)", $envStrict]; yield 'is_defined_test_non_strict' => [$node, "// line 1\narray_key_exists(\"foo\", \$context)", $env]; // is defined test // always defined $node = new ContextVariable('foo', 1); - $node->setAttribute('is_defined_test', true); + $node->enableDefinedTest(); $node->setAttribute('always_defined', true); yield 'is_defined_test_always_defined_strict' => [$node, "// line 1\ntrue", $envStrict]; yield 'is_defined_test_always_defined_non_strict' => [$node, "// line 1\ntrue", $env];