Support 3.11 / PEP 654 syntax (#3016)
This commit is contained in:
parent
712f8b37fb
commit
7f7673d941
@ -53,6 +53,9 @@
|
|||||||
|
|
||||||
### Parser
|
### 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 -->
|
<!-- Changes to the parser or to version autodetection -->
|
||||||
|
|
||||||
### Performance
|
### Performance
|
||||||
|
@ -1296,6 +1296,13 @@ def get_features_used( # noqa: C901
|
|||||||
):
|
):
|
||||||
features.add(Feature.ANN_ASSIGN_EXTENDED_RHS)
|
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
|
return features
|
||||||
|
|
||||||
|
|
||||||
|
@ -915,6 +915,15 @@ def normalize_invisible_parens(
|
|||||||
node.insert_child(index, Leaf(token.LPAR, ""))
|
node.insert_child(index, Leaf(token.LPAR, ""))
|
||||||
node.append_child(Leaf(token.RPAR, ""))
|
node.append_child(Leaf(token.RPAR, ""))
|
||||||
break
|
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)):
|
elif not (isinstance(child, Leaf) and is_multiline_string(child)):
|
||||||
wrap_in_parentheses(node, child, visible=False)
|
wrap_in_parentheses(node, child, visible=False)
|
||||||
|
@ -30,6 +30,7 @@ class TargetVersion(Enum):
|
|||||||
PY38 = 8
|
PY38 = 8
|
||||||
PY39 = 9
|
PY39 = 9
|
||||||
PY310 = 10
|
PY310 = 10
|
||||||
|
PY311 = 11
|
||||||
|
|
||||||
|
|
||||||
class Feature(Enum):
|
class Feature(Enum):
|
||||||
@ -47,6 +48,7 @@ class Feature(Enum):
|
|||||||
PATTERN_MATCHING = 11
|
PATTERN_MATCHING = 11
|
||||||
UNPACKING_ON_FLOW = 12
|
UNPACKING_ON_FLOW = 12
|
||||||
ANN_ASSIGN_EXTENDED_RHS = 13
|
ANN_ASSIGN_EXTENDED_RHS = 13
|
||||||
|
EXCEPT_STAR = 14
|
||||||
FORCE_OPTIONAL_PARENTHESES = 50
|
FORCE_OPTIONAL_PARENTHESES = 50
|
||||||
|
|
||||||
# __future__ flags
|
# __future__ flags
|
||||||
@ -116,6 +118,21 @@ class Feature(Enum):
|
|||||||
Feature.ANN_ASSIGN_EXTENDED_RHS,
|
Feature.ANN_ASSIGN_EXTENDED_RHS,
|
||||||
Feature.PATTERN_MATCHING,
|
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:
|
elif p.type == syms.sliceop:
|
||||||
return NO
|
return NO
|
||||||
|
|
||||||
|
elif p.type == syms.except_clause:
|
||||||
|
if t == token.STAR:
|
||||||
|
return NO
|
||||||
|
|
||||||
return SPACE
|
return SPACE
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ try_stmt: ('try' ':' suite
|
|||||||
with_stmt: 'with' asexpr_test (',' asexpr_test)* ':' suite
|
with_stmt: 'with' asexpr_test (',' asexpr_test)* ':' suite
|
||||||
|
|
||||||
# NB compile.c makes sure that the default except clause is last
|
# 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
|
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
|
||||||
|
|
||||||
# Backward compatibility cruft to support:
|
# 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(
|
self.assertEqual(
|
||||||
black.get_features_used(node), {Feature.ANN_ASSIGN_EXTENDED_RHS}
|
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:
|
def test_get_features_used_for_future_flags(self) -> None:
|
||||||
for src, features in [
|
for src, features in [
|
||||||
|
@ -72,6 +72,11 @@
|
|||||||
"parenthesized_context_managers",
|
"parenthesized_context_managers",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
PY311_CASES: List[str] = [
|
||||||
|
"pep_654",
|
||||||
|
"pep_654_style",
|
||||||
|
]
|
||||||
|
|
||||||
PREVIEW_CASES: List[str] = [
|
PREVIEW_CASES: List[str] = [
|
||||||
# string processing
|
# string processing
|
||||||
"cantfit",
|
"cantfit",
|
||||||
@ -227,6 +232,13 @@ def test_patma_invalid() -> None:
|
|||||||
exc_info.match("Cannot parse: 10:11")
|
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:
|
def test_python_2_hint() -> None:
|
||||||
with pytest.raises(black.parsing.InvalidInput) as exc_info:
|
with pytest.raises(black.parsing.InvalidInput) as exc_info:
|
||||||
assert_format("print 'daylily'", "print 'daylily'")
|
assert_format("print 'daylily'", "print 'daylily'")
|
||||||
|
Loading…
Reference in New Issue
Block a user