Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for PEP 646 #3071

Merged
merged 4 commits into from May 26, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -56,6 +56,8 @@

- [PEP 654](https://peps.python.org/pep-0654/#except) syntax (for example,
`except *ExceptionGroup:`) is now supported (#3016)
- [PEP 646](https://peps.python.org/pep-0646) syntax (for example,
`Array[Batch, *Shape]` or `def fn(*args: *T) -> None`) is now supported (#3071)

<!-- Changes to the parser or to version autodetection -->

Expand Down
12 changes: 12 additions & 0 deletions src/black/__init__.py
Expand Up @@ -1308,6 +1308,18 @@ def get_features_used( # noqa: C901
):
features.add(Feature.EXCEPT_STAR)

elif n.type in {syms.subscriptlist, syms.trailer} and any(
child.type == syms.star_expr for child in n.children
):
features.add(Feature.VARIADIC_GENERICS)

elif (
n.type == syms.tname_star
and len(n.children) == 3
and n.children[2].type == syms.star_expr
):
features.add(Feature.VARIADIC_GENERICS)

return features


Expand Down
2 changes: 2 additions & 0 deletions src/black/mode.py
Expand Up @@ -49,6 +49,7 @@ class Feature(Enum):
UNPACKING_ON_FLOW = 12
ANN_ASSIGN_EXTENDED_RHS = 13
EXCEPT_STAR = 14
VARIADIC_GENERICS = 15
FORCE_OPTIONAL_PARENTHESES = 50

# __future__ flags
Expand Down Expand Up @@ -132,6 +133,7 @@ class Feature(Enum):
Feature.ANN_ASSIGN_EXTENDED_RHS,
Feature.PATTERN_MATCHING,
Feature.EXCEPT_STAR,
Feature.VARIADIC_GENERICS,
},
}

Expand Down
13 changes: 11 additions & 2 deletions src/black/nodes.py
Expand Up @@ -120,6 +120,7 @@
syms.term,
syms.power,
}
TYPED_NAMES: Final = {syms.tname, syms.tname_star}
ASSIGNMENTS: Final = {
"=",
"+=",
Expand Down Expand Up @@ -243,6 +244,14 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
# that, too.
return prevp.prefix

elif (
prevp.type == token.STAR
and parent_type(prevp) == syms.star_expr
and parent_type(prevp.parent) == syms.subscriptlist
):
# No space between typevar tuples.
return NO

elif prevp.type in VARARGS_SPECIALS:
if is_vararg(prevp, within=VARARGS_PARENTS | UNPACKING_PARENTS):
return NO
Expand Down Expand Up @@ -281,7 +290,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
return NO

if t == token.EQUAL:
if prev.type != syms.tname:
if prev.type not in TYPED_NAMES:
return NO

elif prev.type == token.EQUAL:
Expand All @@ -292,7 +301,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
elif prev.type != token.COMMA:
return NO

elif p.type == syms.tname:
elif p.type in TYPED_NAMES:
# type names
if not prev:
prevp = preceding_leaf(p)
Expand Down
9 changes: 5 additions & 4 deletions src/blib2to3/Grammar.txt
Expand Up @@ -24,7 +24,7 @@ parameters: '(' [typedargslist] ')'
# arguments = argument (',' argument)*
# argument = tfpdef ['=' test]
# kwargs = '**' tname [',']
# args = '*' [tname]
# args = '*' [tname_star]
# kwonly_kwargs = (',' argument)* [',' [kwargs]]
# args_kwonly_kwargs = args kwonly_kwargs | kwargs
# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]]
Expand All @@ -34,14 +34,15 @@ parameters: '(' [typedargslist] ')'
# It needs to be fully expanded to allow our LL(1) parser to work on it.

typedargslist: tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [
',' [((tfpdef ['=' test] ',')* ('*' [tname] (',' tname ['=' test])*
',' [((tfpdef ['=' test] ',')* ('*' [tname_star] (',' tname ['=' test])*
[',' ['**' tname [',']]] | '**' tname [','])
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])]
] | ((tfpdef ['=' test] ',')* ('*' [tname] (',' tname ['=' test])*
] | ((tfpdef ['=' test] ',')* ('*' [tname_star] (',' tname ['=' test])*
[',' ['**' tname [',']]] | '**' tname [','])
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])

tname: NAME [':' test]
tname_star: NAME [':' (test|star_expr)]
tfpdef: tname | '(' tfplist ')'
tfplist: tfpdef (',' tfpdef)* [',']

Expand Down Expand Up @@ -163,7 +164,7 @@ listmaker: (namedexpr_test|star_expr) ( old_comp_for | (',' (namedexpr_test|star
testlist_gexp: (namedexpr_test|star_expr) ( old_comp_for | (',' (namedexpr_test|star_expr))* [','] )
lambdef: 'lambda' [varargslist] ':' test
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [',']
subscriptlist: (subscript|star_expr) (',' (subscript|star_expr))* [',']
subscript: test [':=' test] | [test] ':' [test] [sliceop]
sliceop: ':' [test]
exprlist: (expr|star_expr) (',' (expr|star_expr))* [',']
Expand Down
1 change: 1 addition & 0 deletions src/blib2to3/pygram.py
Expand Up @@ -123,6 +123,7 @@ class _python_symbols(Symbols):
tfpdef: int
tfplist: int
tname: int
tname_star: int
trailer: int
try_stmt: int
typedargslist: int
Expand Down
194 changes: 194 additions & 0 deletions tests/data/pep_646.py
@@ -0,0 +1,194 @@
A[*b]
A[*b] = 1
A
del A[*b]
A
A[*b, *b]
A[*b, *b] = 1
A
del A[*b, *b]
A
A[b, *b]
A[b, *b] = 1
A
del A[b, *b]
A
A[*b, b]
A[*b, b] = 1
A
del A[*b, b]
A
A[b, b, *b]
A[b, b, *b] = 1
A
del A[b, b, *b]
A
A[*b, b, b]
A[*b, b, b] = 1
A
del A[*b, b, b]
A
A[b, *b, b]
A[b, *b, b] = 1
A
del A[b, *b, b]
A
A[b, b, *b, b]
A[b, b, *b, b] = 1
A
del A[b, b, *b, b]
A
A[b, *b, b, b]
A[b, *b, b, b] = 1
A
del A[b, *b, b, b]
A
A[A[b, *b, b]]
A[A[b, *b, b]] = 1
A
del A[A[b, *b, b]]
A
A[*A[b, *b, b]]
A[*A[b, *b, b]] = 1
A
del A[*A[b, *b, b]]
A
A[b, ...]
A[b, ...] = 1
A
del A[b, ...]
A
A[*A[b, ...]]
A[*A[b, ...]] = 1
A
del A[*A[b, ...]]
A
l = [1, 2, 3]
A[*l]
A[*l] = 1
A
del A[*l]
A
A[*l, 4]
A[*l, 4] = 1
A
del A[*l, 4]
A
A[0, *l]
A[0, *l] = 1
A
del A[0, *l]
A
A[1:2, *l]
A[1:2, *l] = 1
A
del A[1:2, *l]
A
repr(A[1:2, *l]) == repr(A[1:2, 1, 2, 3])
t = (1, 2, 3)
A[*t]
A[*t] = 1
A
del A[*t]
A
A[*t, 4]
A[*t, 4] = 1
A
del A[*t, 4]
A
A[0, *t]
A[0, *t] = 1
A
del A[0, *t]
A
A[1:2, *t]
A[1:2, *t] = 1
A
del A[1:2, *t]
A
repr(A[1:2, *t]) == repr(A[1:2, 1, 2, 3])


def returns_list():
return [1, 2, 3]


A[returns_list()]
A[returns_list()] = 1
A
del A[returns_list()]
A
A[returns_list(), 4]
A[returns_list(), 4] = 1
A
del A[returns_list(), 4]
A
A[*returns_list()]
A[*returns_list()] = 1
A
del A[*returns_list()]
A
A[*returns_list(), 4]
A[*returns_list(), 4] = 1
A
del A[*returns_list(), 4]
A
A[0, *returns_list()]
A[0, *returns_list()] = 1
A
del A[0, *returns_list()]
A
A[*returns_list(), *returns_list()]
A[*returns_list(), *returns_list()] = 1
A
del A[*returns_list(), *returns_list()]
A
A[1:2, *b]
A[*b, 1:2]
A[1:2, *b, 1:2]
A[*b, 1:2, *b]
A[1:, *b]
A[*b, 1:]
A[1:, *b, 1:]
A[*b, 1:, *b]
A[:1, *b]
A[*b, :1]
A[:1, *b, :1]
A[*b, :1, *b]
A[:, *b]
A[*b, :]
A[:, *b, :]
A[*b, :, *b]
A[a * b()]
A[a * b(), *c, *d(), e * f(g * h)]
A[a * b(), :]
A[a * b(), *c, *d(), e * f(g * h) :]
A[[b] * len(c), :]


def f1(*args: *b):
pass


f1.__annotations__


def f2(*args: *b, arg1):
pass


f2.__annotations__


def f3(*args: *b, arg1: int):
pass


f3.__annotations__


def f4(*args: *b, arg1: int = 2):
pass


f4.__annotations__
6 changes: 6 additions & 0 deletions tests/test_black.py
Expand Up @@ -800,6 +800,12 @@ def test_get_features_used(self) -> None:
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})
node = black.lib2to3_parse("a[*b]")
self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
node = black.lib2to3_parse("a[x, *y(), z] = t")
self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
node = black.lib2to3_parse("def fn(*args: *T): pass")
self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})

def test_get_features_used_for_future_flags(self) -> None:
for src, features in [
Expand Down
1 change: 1 addition & 0 deletions tests/test_format.py
Expand Up @@ -28,6 +28,7 @@
PY311_CASES: List[str] = [
"pep_654",
"pep_654_style",
"pep_646",
]

PREVIEW_CASES: List[str] = [
Expand Down