### Parser
+- [PEP 654](https://peps.python.org/pep-0654/#except) syntax (for example,
+ `except *ExceptionGroup:`) is now supported (#3016)
+
<!-- Changes to the parser or to version autodetection -->
### Performance
):
features.add(Feature.ANN_ASSIGN_EXTENDED_RHS)
+ elif (
+ n.type == syms.except_clause
+ and len(n.children) >= 2
+ and n.children[1].type == token.STAR
+ ):
+ features.add(Feature.EXCEPT_STAR)
+
return features
node.insert_child(index, Leaf(token.LPAR, ""))
node.append_child(Leaf(token.RPAR, ""))
break
+ elif (
+ index == 1
+ and child.type == token.STAR
+ and node.type == syms.except_clause
+ ):
+ # In except* (PEP 654), the star is actually part of
+ # of the keyword. So we need to skip the insertion of
+ # invisible parentheses to work more precisely.
+ continue
elif not (isinstance(child, Leaf) and is_multiline_string(child)):
wrap_in_parentheses(node, child, visible=False)
PY38 = 8
PY39 = 9
PY310 = 10
+ PY311 = 11
class Feature(Enum):
PATTERN_MATCHING = 11
UNPACKING_ON_FLOW = 12
ANN_ASSIGN_EXTENDED_RHS = 13
+ EXCEPT_STAR = 14
FORCE_OPTIONAL_PARENTHESES = 50
# __future__ flags
Feature.ANN_ASSIGN_EXTENDED_RHS,
Feature.PATTERN_MATCHING,
},
+ TargetVersion.PY311: {
+ Feature.F_STRINGS,
+ Feature.NUMERIC_UNDERSCORES,
+ Feature.TRAILING_COMMA_IN_CALL,
+ Feature.TRAILING_COMMA_IN_DEF,
+ Feature.ASYNC_KEYWORDS,
+ Feature.FUTURE_ANNOTATIONS,
+ Feature.ASSIGNMENT_EXPRESSIONS,
+ Feature.RELAXED_DECORATORS,
+ Feature.POS_ONLY_ARGUMENTS,
+ Feature.UNPACKING_ON_FLOW,
+ Feature.ANN_ASSIGN_EXTENDED_RHS,
+ Feature.PATTERN_MATCHING,
+ Feature.EXCEPT_STAR,
+ },
}
elif p.type == syms.sliceop:
return NO
+ elif p.type == syms.except_clause:
+ if t == token.STAR:
+ return NO
+
return SPACE
with_stmt: 'with' asexpr_test (',' asexpr_test)* ':' suite
# NB compile.c makes sure that the default except clause is last
-except_clause: 'except' [test [(',' | 'as') test]]
+except_clause: 'except' ['*'] [test [(',' | 'as') test]]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
# Backward compatibility cruft to support:
--- /dev/null
+try:
+ raise OSError("blah")
+except* ExceptionGroup as e:
+ pass
+
+
+try:
+ async with trio.open_nursery() as nursery:
+ # Make two concurrent calls to child()
+ nursery.start_soon(child)
+ nursery.start_soon(child)
+except* ValueError:
+ pass
+
+try:
+ try:
+ raise ValueError(42)
+ except:
+ try:
+ raise TypeError(int)
+ except* Exception:
+ pass
+ 1 / 0
+except Exception as e:
+ exc = e
+
+try:
+ try:
+ raise FalsyEG("eg", [TypeError(1), ValueError(2)])
+ except* TypeError as e:
+ tes = e
+ raise
+ except* ValueError as e:
+ ves = e
+ pass
+except Exception as e:
+ exc = e
+
+try:
+ try:
+ raise orig
+ except* (TypeError, ValueError) as e:
+ raise SyntaxError(3) from e
+except BaseException as e:
+ exc = e
+
+try:
+ try:
+ raise orig
+ except* OSError as e:
+ raise TypeError(3) from e
+except ExceptionGroup as e:
+ exc = e
--- /dev/null
+try:
+ raise OSError("blah")
+except * ExceptionGroup as e:
+ pass
+
+
+try:
+ async with trio.open_nursery() as nursery:
+ # Make two concurrent calls to child()
+ nursery.start_soon(child)
+ nursery.start_soon(child)
+except *ValueError:
+ pass
+
+try:
+ try:
+ raise ValueError(42)
+ except:
+ try:
+ raise TypeError(int)
+ except *(Exception):
+ pass
+ 1 / 0
+except Exception as e:
+ exc = e
+
+try:
+ try:
+ raise FalsyEG("eg", [TypeError(1), ValueError(2)])
+ except \
+ *TypeError as e:
+ tes = e
+ raise
+ except * ValueError as e:
+ ves = e
+ pass
+except Exception as e:
+ exc = e
+
+try:
+ try:
+ raise orig
+ except *(TypeError, ValueError, *OTHER_EXCEPTIONS) as e:
+ raise SyntaxError(3) from e
+except BaseException as e:
+ exc = e
+
+try:
+ try:
+ raise orig
+ except\
+ * OSError as e:
+ raise TypeError(3) from e
+except ExceptionGroup as e:
+ exc = e
+
+# output
+
+try:
+ raise OSError("blah")
+except* ExceptionGroup as e:
+ pass
+
+
+try:
+ async with trio.open_nursery() as nursery:
+ # Make two concurrent calls to child()
+ nursery.start_soon(child)
+ nursery.start_soon(child)
+except* ValueError:
+ pass
+
+try:
+ try:
+ raise ValueError(42)
+ except:
+ try:
+ raise TypeError(int)
+ except* (Exception):
+ pass
+ 1 / 0
+except Exception as e:
+ exc = e
+
+try:
+ try:
+ raise FalsyEG("eg", [TypeError(1), ValueError(2)])
+ except* TypeError as e:
+ tes = e
+ raise
+ except* ValueError as e:
+ ves = e
+ pass
+except Exception as e:
+ exc = e
+
+try:
+ try:
+ raise orig
+ except* (TypeError, ValueError, *OTHER_EXCEPTIONS) as e:
+ raise SyntaxError(3) from e
+except BaseException as e:
+ exc = e
+
+try:
+ try:
+ raise orig
+ except* OSError as e:
+ raise TypeError(3) from e
+except ExceptionGroup as e:
+ exc = e
self.assertEqual(
black.get_features_used(node), {Feature.ANN_ASSIGN_EXTENDED_RHS}
)
+ node = black.lib2to3_parse("try: pass\nexcept Something: pass")
+ self.assertEqual(black.get_features_used(node), set())
+ node = black.lib2to3_parse("try: pass\nexcept (*Something,): pass")
+ self.assertEqual(black.get_features_used(node), set())
+ node = black.lib2to3_parse("try: pass\nexcept *Group: pass")
+ self.assertEqual(black.get_features_used(node), {Feature.EXCEPT_STAR})
def test_get_features_used_for_future_flags(self) -> None:
for src, features in [
"parenthesized_context_managers",
]
+PY311_CASES: List[str] = [
+ "pep_654",
+ "pep_654_style",
+]
+
PREVIEW_CASES: List[str] = [
# string processing
"cantfit",
exc_info.match("Cannot parse: 10:11")
+@pytest.mark.parametrize("filename", PY311_CASES)
+def test_python_311(filename: str) -> None:
+ source, expected = read_data(filename)
+ mode = black.Mode(target_versions={black.TargetVersion.PY311})
+ assert_format(source, expected, mode, minimum_version=(3, 11))
+
+
def test_python_2_hint() -> None:
with pytest.raises(black.parsing.InvalidInput) as exc_info:
assert_format("print 'daylily'", "print 'daylily'")