From 2301484d50aec68ee08bb220740829acb88e3df7 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Sun, 18 Jan 2026 09:58:17 -0800 Subject: [PATCH 1/4] Add a unittest to expose the issue reported in gh-1436 --- libcst/_nodes/tests/test_match.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libcst/_nodes/tests/test_match.py b/libcst/_nodes/tests/test_match.py index 2335b7c3..19566c28 100644 --- a/libcst/_nodes/tests/test_match.py +++ b/libcst/_nodes/tests/test_match.py @@ -226,6 +226,20 @@ class MatchTest(CSTNodeTest): ), body=cst.SimpleStatementSuite((cst.Pass(),)), ), + cst.MatchCase( # rest with trailing comma - valid syntax (issue #1436) + pattern=cst.MatchMapping( + [ + cst.MatchMappingElement( + key=cst.SimpleString('"a"'), + pattern=cst.MatchValue(cst.Integer("1")), + comma=cst.Comma(whitespace_after=cst.SimpleWhitespace(" ")), + ), + ], + rest=cst.Name("keys"), + trailing_comma=cst.Comma(), + ), + body=cst.SimpleStatementSuite((cst.Pass(),)), + ), ], ), "code": ( @@ -234,6 +248,7 @@ class MatchTest(CSTNodeTest): + ' case {"a": None,"b": None}: pass\n' + ' case {"a": None,}: pass\n' + " case {**rest}: pass\n" + + ' case {"a": 1, **keys,}: pass\n' ), "parser": parser, }, From 64b5f4ecb4e1a97c91a356443c9c4b31b6295a6e Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Sun, 18 Jan 2026 10:10:05 -0800 Subject: [PATCH 2/4] Fix issue reported in gh-1436 --- libcst/_nodes/statement.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/libcst/_nodes/statement.py b/libcst/_nodes/statement.py index cdc49edc..5546f143 100644 --- a/libcst/_nodes/statement.py +++ b/libcst/_nodes/statement.py @@ -3245,8 +3245,6 @@ class MatchMapping(MatchPattern): rpar: Sequence[RightParen] = () def _validate(self) -> None: - if isinstance(self.trailing_comma, Comma) and self.rest is not None: - raise CSTValidationError("Cannot have a trailing comma without **rest") super(MatchMapping, self)._validate() def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "MatchMapping": From 6caf078f6234ebf33ebe3c59c37f4c4b76608bb3 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Sun, 18 Jan 2026 10:12:56 -0800 Subject: [PATCH 3/4] formatting --- libcst/_nodes/tests/test_match.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libcst/_nodes/tests/test_match.py b/libcst/_nodes/tests/test_match.py index 19566c28..55533293 100644 --- a/libcst/_nodes/tests/test_match.py +++ b/libcst/_nodes/tests/test_match.py @@ -232,7 +232,9 @@ class MatchTest(CSTNodeTest): cst.MatchMappingElement( key=cst.SimpleString('"a"'), pattern=cst.MatchValue(cst.Integer("1")), - comma=cst.Comma(whitespace_after=cst.SimpleWhitespace(" ")), + comma=cst.Comma( + whitespace_after=cst.SimpleWhitespace(" ") + ), ), ], rest=cst.Name("keys"), From d28f43d4ce6c9b803fe3dd3afa4a1c0403c744c0 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Mon, 19 Jan 2026 09:44:45 -0800 Subject: [PATCH 4/4] Add roundtrip tests for trailing comma after `**rest` --- native/libcst/tests/fixtures/malicious_match.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/libcst/tests/fixtures/malicious_match.py b/native/libcst/tests/fixtures/malicious_match.py index 54840022..8277ed88 100644 --- a/native/libcst/tests/fixtures/malicious_match.py +++ b/native/libcst/tests/fixtures/malicious_match.py @@ -39,4 +39,6 @@ case 1, 2: pass case ( Foo ( ) ) : pass case (lol) if ( True , ) :pass + case {"a": 1, **keys,} : pass + case {**rest,} : pass