Support 3.11 / PEP 654 syntax (#3016)
This commit is contained in:
parent
712f8b37fb
commit
7f7673d941
@ -53,6 +53,9 @@
|
||||
|
||||
### 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
|
||||
|
@ -1296,6 +1296,13 @@ def get_features_used( # noqa: C901
|
||||
):
|
||||
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
|
||||
|
||||
|
||||
|
@ -915,6 +915,15 @@ def normalize_invisible_parens(
|
||||
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)
|
||||
|
@ -30,6 +30,7 @@ class TargetVersion(Enum):
|
||||
PY38 = 8
|
||||
PY39 = 9
|
||||
PY310 = 10
|
||||
PY311 = 11
|
||||
|
||||
|
||||
class Feature(Enum):
|
||||
@ -47,6 +48,7 @@ class Feature(Enum):
|
||||
PATTERN_MATCHING = 11
|
||||
UNPACKING_ON_FLOW = 12
|
||||
ANN_ASSIGN_EXTENDED_RHS = 13
|
||||
EXCEPT_STAR = 14
|
||||
FORCE_OPTIONAL_PARENTHESES = 50
|
||||
|
||||
# __future__ flags
|
||||
@ -116,6 +118,21 @@ class Feature(Enum):
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -401,6 +401,10 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
|
||||
elif p.type == syms.sliceop:
|
||||
return NO
|
||||
|
||||
elif p.type == syms.except_clause:
|
||||
if t == token.STAR:
|
||||
return NO
|
||||
|
||||
return SPACE
|
||||
|
||||
|
||||
|
@ -118,7 +118,7 @@ try_stmt: ('try' ':' suite
|
||||
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:
|
||||
|
53
tests/data/pep_654.py
Normal file
53
tests/data/pep_654.py
Normal file
@ -0,0 +1,53 @@
|
||||
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
|
111
tests/data/pep_654_style.py
Normal file
111
tests/data/pep_654_style.py
Normal file
@ -0,0 +1,111 @@
|
||||
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
|
@ -794,6 +794,12 @@ def test_get_features_used(self) -> None:
|
||||
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 [
|
||||
|
@ -72,6 +72,11 @@
|
||||
"parenthesized_context_managers",
|
||||
]
|
||||
|
||||
PY311_CASES: List[str] = [
|
||||
"pep_654",
|
||||
"pep_654_style",
|
||||
]
|
||||
|
||||
PREVIEW_CASES: List[str] = [
|
||||
# string processing
|
||||
"cantfit",
|
||||
@ -227,6 +232,13 @@ def test_patma_invalid() -> None:
|
||||
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'")
|
||||
|
Loading…
Reference in New Issue
Block a user