From a2b023397eebdbbcae5fbb9cec5c6739d1f5d1ae Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 23 Aug 2026 09:50:46 +0200 Subject: [PATCH] Fix an empty destructuring pattern triggering a PHP fatal error instead of a SyntaxError --- CHANGELOG | 1 + .../Infix/AssignmentExpressionParser.php | 3 +++ tests/ExpressionParserTest.php | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 185a82a6f..38e3176a4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ # 3.29.0 (2026-XX-XX) * Add documentation comments to attach metadata to nodes (experimental) + * Fix an empty destructuring pattern triggering a PHP fatal error instead of a `SyntaxError` * Fix sequence destructuring of iterators throwing a `TypeError` * Add `TempestMarkdown` to use `tempest/markdown` as the `markdown_to_html` converter * Fix imported macros not resolving their own template-level macro imports diff --git a/src/ExpressionParser/Infix/AssignmentExpressionParser.php b/src/ExpressionParser/Infix/AssignmentExpressionParser.php index 291d68bf5..451d0f968 100644 --- a/src/ExpressionParser/Infix/AssignmentExpressionParser.php +++ b/src/ExpressionParser/Infix/AssignmentExpressionParser.php @@ -50,6 +50,9 @@ class AssignmentExpressionParser extends BinaryOperatorExpressionParser }; if ($left instanceof ArrayExpression) { + if (!$left->getKeyValuePairs()) { + throw new SyntaxError('Cannot destructure to an empty list of variables.', $token->getLine(), $parser->getStream()->getSourceContext()); + } foreach ($left->getKeyValuePairs() as $i => $pair) { if ($pair['value'] instanceof ContextVariable && !$pair['value'] instanceof AssignContextVariable) { $left->setNode(2 * $i + 1, new AssignContextVariable($pair['value']->getAttribute('name'), $pair['value']->getTemplateLine())); diff --git a/tests/ExpressionParserTest.php b/tests/ExpressionParserTest.php index dc69c812b..7709e2948 100644 --- a/tests/ExpressionParserTest.php +++ b/tests/ExpressionParserTest.php @@ -268,6 +268,25 @@ class ExpressionParserTest extends TestCase $this->assertSame('third', $pairs[2]['value']->getAttribute('name')); } + /** + * @dataProvider getEmptyDestructuringTests + */ + #[DataProvider('getEmptyDestructuringTests')] + public function testEmptyDestructuringThrows(string $template): void + { + $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]); + + $this->expectException(SyntaxError::class); + $this->expectExceptionMessage('Cannot destructure to an empty list of variables'); + $env->compileSource(new Source($template, 'index')); + } + + public static function getEmptyDestructuringTests() + { + yield ['{% do [] = values %}']; + yield ['{% do {} = values %}']; + } + public function testObjectDestructuringUsesAssignmentTargets(): void { $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);