From 86c830ef45a9f1b06b3ed52c517147db567ef7ed Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Tue, 1 Sep 2026 14:06:43 +0500 Subject: [PATCH 1/3] Fix the default filter fallback reusing a null-safe temporary variable --- CHANGELOG | 1 + src/Node/Expression/Filter/DefaultFilter.php | 2 +- tests/ExpressionParserTest.php | 21 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 991e99dfc..47e3a7240 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.29.0 (2026-XX-XX) + * Fix the `default` filter fallback emitting an undefined variable warning when it uses the null-safe operator * Fix the `matches` operator silently treating PCRE execution errors as non-matches * Add the `HtmlExtension::htmlAttrValue()` method to resolve a single HTML attribute value the way the `html_attr` function renders it * Fix `html_attr` JSON encoding a `Stringable` value in a `data-*` attribute instead of using its string representation diff --git a/src/Node/Expression/Filter/DefaultFilter.php b/src/Node/Expression/Filter/DefaultFilter.php index fc64b88a9..cef374829 100644 --- a/src/Node/Expression/Filter/DefaultFilter.php +++ b/src/Node/Expression/Filter/DefaultFilter.php @@ -55,7 +55,7 @@ class DefaultFilter extends FilterExpression if ('default' === $name && ($node instanceof ContextVariable || $node instanceof GetAttrExpression)) { $test = new DefinedTest(clone $node, new TwigTest('defined', null, ['always_allowed_in_sandbox' => true]), new EmptyNode(), $node->getTemplateLine()); - $false = \count($arguments) ? $arguments->getNode('0') : new ConstantExpression('', $node->getTemplateLine()); + $false = \count($arguments) ? clone $arguments->getNode('0') : new ConstantExpression('', $node->getTemplateLine()); $node = new ConditionalTernary($test, $default, $false, $node->getTemplateLine()); } else { diff --git a/tests/ExpressionParserTest.php b/tests/ExpressionParserTest.php index 5754bf330..649e2b84c 100644 --- a/tests/ExpressionParserTest.php +++ b/tests/ExpressionParserTest.php @@ -445,6 +445,27 @@ class ExpressionParserTest extends TestCase ['foo' => (object) ['bar' => (object) ['baz' => null]]], '', ], + // default filter fallback + [ + '{{ foo?.bar|default(foo?.baz) }}', + ['foo' => null], + '', + ], + [ + '{{ foo?.bar|default(foo?.baz.qux) }}', + ['foo' => null], + '', + ], + [ + '{{ foo?.bar|default(foo?.baz) }}', + ['foo' => (object) ['bar' => null, 'baz' => 'qux']], + 'qux', + ], + [ + '{{ foo?.bar|default(foo?.baz) }}', + ['foo' => (object) ['bar' => 'corge', 'baz' => 'qux']], + 'corge', + ], ]; } From 0b4199c5224013efea6ae043742a1c511af64423 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 3 Sep 2026 08:32:22 +0200 Subject: [PATCH 2/3] Strengthen the default filter regression test --- tests/ExpressionParserTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/ExpressionParserTest.php b/tests/ExpressionParserTest.php index 649e2b84c..3bdfea734 100644 --- a/tests/ExpressionParserTest.php +++ b/tests/ExpressionParserTest.php @@ -456,6 +456,11 @@ class ExpressionParserTest extends TestCase ['foo' => null], '', ], + [ + '{% for foo in foos %}[{{ foo?.bar|default(foo?.baz) }}]{% endfor %}', + ['foos' => [(object) ['bar' => 'corge', 'baz' => 'grault'], (object) ['baz' => 'qux']]], + '[corge][qux]', + ], [ '{{ foo?.bar|default(foo?.baz) }}', ['foo' => (object) ['bar' => null, 'baz' => 'qux']], From 2d0c30b07528dee7a1409bf2be3c5a779225e041 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 3 Sep 2026 08:49:11 +0200 Subject: [PATCH 3/3] Remove redundant default filter cases --- tests/ExpressionParserTest.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/ExpressionParserTest.php b/tests/ExpressionParserTest.php index 3bdfea734..26ee469f2 100644 --- a/tests/ExpressionParserTest.php +++ b/tests/ExpressionParserTest.php @@ -461,16 +461,6 @@ class ExpressionParserTest extends TestCase ['foos' => [(object) ['bar' => 'corge', 'baz' => 'grault'], (object) ['baz' => 'qux']]], '[corge][qux]', ], - [ - '{{ foo?.bar|default(foo?.baz) }}', - ['foo' => (object) ['bar' => null, 'baz' => 'qux']], - 'qux', - ], - [ - '{{ foo?.bar|default(foo?.baz) }}', - ['foo' => (object) ['bar' => 'corge', 'baz' => 'qux']], - 'corge', - ], ]; }