Skip to content

Commit 3b5329a

Browse files
feat: add support for PEP758 (#1401)
PEP758 removes the requirement for parentheses to surround exceptions in except and except* expressions when 'as' is not present. This pr implements support for parsing these types of statements
1 parent 48668df commit 3b5329a

File tree

3 files changed

+110
-9
lines changed

3 files changed

+110
-9
lines changed

libcst/_nodes/tests/test_try.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,34 @@ class TryTest(CSTNodeTest):
344344
),
345345
"code": "try: pass\nexcept foo()as bar: pass\n",
346346
},
347+
# PEP758 - Multiple exceptions with no parentheses
348+
{
349+
"node": cst.Try(
350+
cst.SimpleStatementSuite((cst.Pass(),)),
351+
handlers=[
352+
cst.ExceptHandler(
353+
cst.SimpleStatementSuite((cst.Pass(),)),
354+
type=cst.Tuple(
355+
elements=[
356+
cst.Element(
357+
value=cst.Name(
358+
value="ValueError",
359+
),
360+
),
361+
cst.Element(
362+
value=cst.Name(
363+
value="RuntimeError",
364+
),
365+
),
366+
],
367+
lpar=[],
368+
rpar=[],
369+
),
370+
)
371+
],
372+
),
373+
"code": "try: pass\nexcept ValueError, RuntimeError: pass\n",
374+
},
347375
)
348376
)
349377
def test_valid(self, **kwargs: Any) -> None:
@@ -576,6 +604,38 @@ class TryStarTest(CSTNodeTest):
576604
"parser": native_parse_statement,
577605
"expected_position": CodeRange((1, 0), (5, 13)),
578606
},
607+
# PEP758 - Multiple exceptions with no parentheses
608+
{
609+
"node": cst.TryStar(
610+
cst.SimpleStatementSuite((cst.Pass(),)),
611+
handlers=[
612+
cst.ExceptStarHandler(
613+
cst.SimpleStatementSuite((cst.Pass(),)),
614+
type=cst.Tuple(
615+
elements=[
616+
cst.Element(
617+
value=cst.Name(
618+
value="ValueError",
619+
),
620+
comma=cst.Comma(
621+
whitespace_after=cst.SimpleWhitespace(" ")
622+
),
623+
),
624+
cst.Element(
625+
value=cst.Name(
626+
value="RuntimeError",
627+
),
628+
),
629+
],
630+
lpar=[],
631+
rpar=[],
632+
),
633+
)
634+
],
635+
),
636+
"code": "try: pass\nexcept* ValueError, RuntimeError: pass\n",
637+
"parser": native_parse_statement,
638+
},
579639
)
580640
)
581641
def test_valid(self, **kwargs: Any) -> None:

native/libcst/src/parser/grammar.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -554,12 +554,21 @@ parser! {
554554
}
555555

556556
// Except statement
557-
558557
rule except_block() -> ExceptHandler<'input, 'a>
559558
= kw:lit("except") e:expression() a:(k:lit("as") n:name() {(k, n)})?
560559
col:lit(":") b:block() {
561560
make_except(kw, Some(e), a, col, b)
562561
}
562+
/ kw:lit("except") e:expression() other:(c:comma() ex:expression() {(c, ex)})+ tc:(c:comma())?
563+
col:lit(":") b:block() {
564+
let tuple = Expression::Tuple(Box::new(Tuple {
565+
elements: comma_separate(expr_to_element(e), other.into_iter().map(|(comma, expr)| (comma, expr_to_element(expr))).collect(), tc),
566+
lpar: vec![],
567+
rpar: vec![],
568+
}));
569+
570+
make_except(kw, Some(tuple), None, col, b)
571+
}
563572
/ kw:lit("except") col:lit(":") b:block() {
564573
make_except(kw, None, None, col, b)
565574
}
@@ -569,6 +578,16 @@ parser! {
569578
a:(k:lit("as") n:name() {(k, n)})? col:lit(":") b:block() {
570579
make_except_star(kw, star, e, a, col, b)
571580
}
581+
/ kw:lit("except") star:lit("*") e:expression() other:(c:comma() ex:expression() {(c, ex)})+ tc:(c:comma())?
582+
col:lit(":") b:block() {
583+
let tuple = Expression::Tuple(Box::new(Tuple {
584+
elements: comma_separate(expr_to_element(e), other.into_iter().map(|(comma, expr)| (comma, expr_to_element(expr))).collect(), tc),
585+
lpar: vec![],
586+
rpar: vec![],
587+
}));
588+
589+
make_except_star(kw, star, tuple, None, col, b)
590+
}
572591

573592
rule finally_block() -> Finally<'input, 'a>
574593
= kw:lit("finally") col:lit(":") b:block() {
@@ -1550,22 +1569,22 @@ parser! {
15501569
rule separated<El, Sep>(el: rule<El>, sep: rule<Sep>) -> (El, Vec<(Sep, El)>)
15511570
= e:el() rest:(s:sep() e:el() {(s, e)})* {(e, rest)}
15521571

1553-
rule traced<T>(e: rule<T>) -> T =
1554-
&(_* {
1572+
rule traced<T>(e: rule<T>) -> T =
1573+
&(_* {
15551574
#[cfg(feature = "trace")]
15561575
{
15571576
println!("[PEG_INPUT_START]");
15581577
println!("{}", input);
15591578
println!("[PEG_TRACE_START]");
15601579
}
1561-
})
1562-
e:e()? {?
1580+
})
1581+
e:e()? {?
15631582
#[cfg(feature = "trace")]
1564-
println!("[PEG_TRACE_STOP]");
1565-
e.ok_or("")
1566-
}
1583+
println!("[PEG_TRACE_STOP]");
1584+
e.ok_or("")
1585+
}
15671586

1568-
}
1587+
}
15691588
}
15701589

15711590
#[allow(clippy::too_many_arguments)]

native/libcst/tests/fixtures/terrible_tries.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,25 @@
6969
pass
7070

7171
#9
72+
73+
try:
74+
pass
75+
except (foo, bar):
76+
pass
77+
78+
try:
79+
pass
80+
except foo, bar:
81+
pass
82+
83+
try:
84+
pass
85+
except (foo, bar), baz:
86+
pass
87+
else:
88+
pass
89+
90+
try:
91+
pass
92+
except* something, somethingelse:
93+
pass

0 commit comments

Comments
 (0)