Add support for PEP 695 syntax (#3703)
This commit is contained in:
parent
a538ab7663
commit
3aad6e385b
@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
<!-- Changes to the parser or to version autodetection -->
|
<!-- Changes to the parser or to version autodetection -->
|
||||||
|
|
||||||
|
- Add support for the new PEP 695 syntax in Python 3.12 (#3703)
|
||||||
|
|
||||||
### Performance
|
### Performance
|
||||||
|
|
||||||
<!-- Changes that improve Black's performance. -->
|
<!-- Changes that improve Black's performance. -->
|
||||||
|
@ -214,4 +214,6 @@ filterwarnings = [
|
|||||||
# aiohttp is using deprecated cgi modules - Safe to remove when fixed:
|
# aiohttp is using deprecated cgi modules - Safe to remove when fixed:
|
||||||
# https://github.com/aio-libs/aiohttp/issues/6905
|
# https://github.com/aio-libs/aiohttp/issues/6905
|
||||||
'''ignore:'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning''',
|
'''ignore:'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning''',
|
||||||
|
# Work around https://github.com/pytest-dev/pytest/issues/10977 for Python 3.12
|
||||||
|
'''ignore:(Attribute s|Attribute n|ast.Str|ast.Bytes|ast.NameConstant|ast.Num) is deprecated and will be removed in Python 3.14:DeprecationWarning'''
|
||||||
]
|
]
|
||||||
|
@ -1275,6 +1275,9 @@ def get_features_used( # noqa: C901
|
|||||||
):
|
):
|
||||||
features.add(Feature.VARIADIC_GENERICS)
|
features.add(Feature.VARIADIC_GENERICS)
|
||||||
|
|
||||||
|
elif n.type in (syms.type_stmt, syms.typeparams):
|
||||||
|
features.add(Feature.TYPE_PARAMS)
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
|
|
||||||
|
@ -215,6 +215,18 @@ def visit_stmt(
|
|||||||
|
|
||||||
yield from self.visit(child)
|
yield from self.visit(child)
|
||||||
|
|
||||||
|
def visit_typeparams(self, node: Node) -> Iterator[Line]:
|
||||||
|
yield from self.visit_default(node)
|
||||||
|
node.children[0].prefix = ""
|
||||||
|
|
||||||
|
def visit_typevartuple(self, node: Node) -> Iterator[Line]:
|
||||||
|
yield from self.visit_default(node)
|
||||||
|
node.children[1].prefix = ""
|
||||||
|
|
||||||
|
def visit_paramspec(self, node: Node) -> Iterator[Line]:
|
||||||
|
yield from self.visit_default(node)
|
||||||
|
node.children[1].prefix = ""
|
||||||
|
|
||||||
def visit_dictsetmaker(self, node: Node) -> Iterator[Line]:
|
def visit_dictsetmaker(self, node: Node) -> Iterator[Line]:
|
||||||
if Preview.wrap_long_dict_values_in_parens in self.mode:
|
if Preview.wrap_long_dict_values_in_parens in self.mode:
|
||||||
for i, child in enumerate(node.children):
|
for i, child in enumerate(node.children):
|
||||||
|
@ -30,6 +30,7 @@ class TargetVersion(Enum):
|
|||||||
PY39 = 9
|
PY39 = 9
|
||||||
PY310 = 10
|
PY310 = 10
|
||||||
PY311 = 11
|
PY311 = 11
|
||||||
|
PY312 = 12
|
||||||
|
|
||||||
|
|
||||||
class Feature(Enum):
|
class Feature(Enum):
|
||||||
@ -51,6 +52,7 @@ class Feature(Enum):
|
|||||||
VARIADIC_GENERICS = 15
|
VARIADIC_GENERICS = 15
|
||||||
DEBUG_F_STRINGS = 16
|
DEBUG_F_STRINGS = 16
|
||||||
PARENTHESIZED_CONTEXT_MANAGERS = 17
|
PARENTHESIZED_CONTEXT_MANAGERS = 17
|
||||||
|
TYPE_PARAMS = 18
|
||||||
FORCE_OPTIONAL_PARENTHESES = 50
|
FORCE_OPTIONAL_PARENTHESES = 50
|
||||||
|
|
||||||
# __future__ flags
|
# __future__ flags
|
||||||
@ -143,6 +145,25 @@ class Feature(Enum):
|
|||||||
Feature.EXCEPT_STAR,
|
Feature.EXCEPT_STAR,
|
||||||
Feature.VARIADIC_GENERICS,
|
Feature.VARIADIC_GENERICS,
|
||||||
},
|
},
|
||||||
|
TargetVersion.PY312: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.DEBUG_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.PARENTHESIZED_CONTEXT_MANAGERS,
|
||||||
|
Feature.PATTERN_MATCHING,
|
||||||
|
Feature.EXCEPT_STAR,
|
||||||
|
Feature.VARIADIC_GENERICS,
|
||||||
|
Feature.TYPE_PARAMS,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,11 +12,17 @@ file_input: (NEWLINE | stmt)* ENDMARKER
|
|||||||
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
|
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
|
||||||
eval_input: testlist NEWLINE* ENDMARKER
|
eval_input: testlist NEWLINE* ENDMARKER
|
||||||
|
|
||||||
|
typevar: NAME [':' expr]
|
||||||
|
paramspec: '**' NAME
|
||||||
|
typevartuple: '*' NAME
|
||||||
|
typeparam: typevar | paramspec | typevartuple
|
||||||
|
typeparams: '[' typeparam (',' typeparam)* [','] ']'
|
||||||
|
|
||||||
decorator: '@' namedexpr_test NEWLINE
|
decorator: '@' namedexpr_test NEWLINE
|
||||||
decorators: decorator+
|
decorators: decorator+
|
||||||
decorated: decorators (classdef | funcdef | async_funcdef)
|
decorated: decorators (classdef | funcdef | async_funcdef)
|
||||||
async_funcdef: ASYNC funcdef
|
async_funcdef: ASYNC funcdef
|
||||||
funcdef: 'def' NAME parameters ['->' test] ':' suite
|
funcdef: 'def' NAME [typeparams] parameters ['->' test] ':' suite
|
||||||
parameters: '(' [typedargslist] ')'
|
parameters: '(' [typedargslist] ')'
|
||||||
|
|
||||||
# The following definition for typedarglist is equivalent to this set of rules:
|
# The following definition for typedarglist is equivalent to this set of rules:
|
||||||
@ -74,7 +80,7 @@ vfplist: vfpdef (',' vfpdef)* [',']
|
|||||||
|
|
||||||
stmt: simple_stmt | compound_stmt
|
stmt: simple_stmt | compound_stmt
|
||||||
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
|
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
|
||||||
small_stmt: (expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt |
|
small_stmt: (type_stmt | expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt |
|
||||||
import_stmt | global_stmt | exec_stmt | assert_stmt)
|
import_stmt | global_stmt | exec_stmt | assert_stmt)
|
||||||
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
|
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
|
||||||
('=' (yield_expr|testlist_star_expr))*)
|
('=' (yield_expr|testlist_star_expr))*)
|
||||||
@ -105,6 +111,7 @@ dotted_name: NAME ('.' NAME)*
|
|||||||
global_stmt: ('global' | 'nonlocal') NAME (',' NAME)*
|
global_stmt: ('global' | 'nonlocal') NAME (',' NAME)*
|
||||||
exec_stmt: 'exec' expr ['in' test [',' test]]
|
exec_stmt: 'exec' expr ['in' test [',' test]]
|
||||||
assert_stmt: 'assert' test [',' test]
|
assert_stmt: 'assert' test [',' test]
|
||||||
|
type_stmt: "type" NAME [typeparams] '=' expr
|
||||||
|
|
||||||
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt | match_stmt
|
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt | match_stmt
|
||||||
async_stmt: ASYNC (funcdef | with_stmt | for_stmt)
|
async_stmt: ASYNC (funcdef | with_stmt | for_stmt)
|
||||||
@ -174,7 +181,7 @@ dictsetmaker: ( ((test ':' asexpr_test | '**' expr)
|
|||||||
((test [':=' test] | star_expr)
|
((test [':=' test] | star_expr)
|
||||||
(comp_for | (',' (test [':=' test] | star_expr))* [','])) )
|
(comp_for | (',' (test [':=' test] | star_expr))* [','])) )
|
||||||
|
|
||||||
classdef: 'class' NAME ['(' [arglist] ')'] ':' suite
|
classdef: 'class' NAME [typeparams] ['(' [arglist] ')'] ':' suite
|
||||||
|
|
||||||
arglist: argument (',' argument)* [',']
|
arglist: argument (',' argument)* [',']
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ class _python_symbols(Symbols):
|
|||||||
old_test: int
|
old_test: int
|
||||||
or_test: int
|
or_test: int
|
||||||
parameters: int
|
parameters: int
|
||||||
|
paramspec: int
|
||||||
pass_stmt: int
|
pass_stmt: int
|
||||||
pattern: int
|
pattern: int
|
||||||
patterns: int
|
patterns: int
|
||||||
@ -126,7 +127,12 @@ class _python_symbols(Symbols):
|
|||||||
tname_star: int
|
tname_star: int
|
||||||
trailer: int
|
trailer: int
|
||||||
try_stmt: int
|
try_stmt: int
|
||||||
|
type_stmt: int
|
||||||
typedargslist: int
|
typedargslist: int
|
||||||
|
typeparam: int
|
||||||
|
typeparams: int
|
||||||
|
typevar: int
|
||||||
|
typevartuple: int
|
||||||
varargslist: int
|
varargslist: int
|
||||||
vfpdef: int
|
vfpdef: int
|
||||||
vfplist: int
|
vfplist: int
|
||||||
|
13
tests/data/py_312/type_aliases.py
Normal file
13
tests/data/py_312/type_aliases.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
type A=int
|
||||||
|
type Gen[T]=list[T]
|
||||||
|
|
||||||
|
type = aliased
|
||||||
|
print(type(42))
|
||||||
|
|
||||||
|
# output
|
||||||
|
|
||||||
|
type A = int
|
||||||
|
type Gen[T] = list[T]
|
||||||
|
|
||||||
|
type = aliased
|
||||||
|
print(type(42))
|
57
tests/data/py_312/type_params.py
Normal file
57
tests/data/py_312/type_params.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
def func [T ](): pass
|
||||||
|
async def func [ T ] (): pass
|
||||||
|
class C[ T ] : pass
|
||||||
|
|
||||||
|
def all_in[T : int,U : (bytes, str),* Ts,**P](): pass
|
||||||
|
|
||||||
|
def really_long[WhatIsTheLongestTypeVarNameYouCanThinkOfEnoughToMakeBlackSplitThisLine](): pass
|
||||||
|
|
||||||
|
def even_longer[WhatIsTheLongestTypeVarNameYouCanThinkOfEnoughToMakeBlackSplitThisLine: WhatIfItHadABound](): pass
|
||||||
|
|
||||||
|
def it_gets_worse[WhatIsTheLongestTypeVarNameYouCanThinkOfEnoughToMakeBlackSplitThisLine, ItCouldBeGenericOverMultipleTypeVars](): pass
|
||||||
|
|
||||||
|
def magic[Trailing, Comma,](): pass
|
||||||
|
|
||||||
|
# output
|
||||||
|
|
||||||
|
|
||||||
|
def func[T]():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def func[T]():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class C[T]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def all_in[T: int, U: (bytes, str), *Ts, **P]():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def really_long[
|
||||||
|
WhatIsTheLongestTypeVarNameYouCanThinkOfEnoughToMakeBlackSplitThisLine
|
||||||
|
]():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def even_longer[
|
||||||
|
WhatIsTheLongestTypeVarNameYouCanThinkOfEnoughToMakeBlackSplitThisLine: WhatIfItHadABound
|
||||||
|
]():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def it_gets_worse[
|
||||||
|
WhatIsTheLongestTypeVarNameYouCanThinkOfEnoughToMakeBlackSplitThisLine,
|
||||||
|
ItCouldBeGenericOverMultipleTypeVars,
|
||||||
|
]():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def magic[
|
||||||
|
Trailing,
|
||||||
|
Comma,
|
||||||
|
]():
|
||||||
|
pass
|
@ -271,6 +271,15 @@ def test_pep_572_version_detection(self) -> None:
|
|||||||
versions = black.detect_target_versions(root)
|
versions = black.detect_target_versions(root)
|
||||||
self.assertIn(black.TargetVersion.PY38, versions)
|
self.assertIn(black.TargetVersion.PY38, versions)
|
||||||
|
|
||||||
|
def test_pep_695_version_detection(self) -> None:
|
||||||
|
for file in ("type_aliases", "type_params"):
|
||||||
|
source, _ = read_data("py_312", file)
|
||||||
|
root = black.lib2to3_parse(source)
|
||||||
|
features = black.get_features_used(root)
|
||||||
|
self.assertIn(black.Feature.TYPE_PARAMS, features)
|
||||||
|
versions = black.detect_target_versions(root)
|
||||||
|
self.assertIn(black.TargetVersion.PY312, versions)
|
||||||
|
|
||||||
def test_expression_ff(self) -> None:
|
def test_expression_ff(self) -> None:
|
||||||
source, expected = read_data("simple_cases", "expression.py")
|
source, expected = read_data("simple_cases", "expression.py")
|
||||||
tmp_file = Path(black.dump_to_file(source))
|
tmp_file = Path(black.dump_to_file(source))
|
||||||
@ -1533,14 +1542,25 @@ def test_infer_target_version(self) -> None:
|
|||||||
for version, expected in [
|
for version, expected in [
|
||||||
("3.6", [TargetVersion.PY36]),
|
("3.6", [TargetVersion.PY36]),
|
||||||
("3.11.0rc1", [TargetVersion.PY311]),
|
("3.11.0rc1", [TargetVersion.PY311]),
|
||||||
(">=3.10", [TargetVersion.PY310, TargetVersion.PY311]),
|
(">=3.10", [TargetVersion.PY310, TargetVersion.PY311, TargetVersion.PY312]),
|
||||||
(">=3.10.6", [TargetVersion.PY310, TargetVersion.PY311]),
|
(
|
||||||
|
">=3.10.6",
|
||||||
|
[TargetVersion.PY310, TargetVersion.PY311, TargetVersion.PY312],
|
||||||
|
),
|
||||||
("<3.6", [TargetVersion.PY33, TargetVersion.PY34, TargetVersion.PY35]),
|
("<3.6", [TargetVersion.PY33, TargetVersion.PY34, TargetVersion.PY35]),
|
||||||
(">3.7,<3.10", [TargetVersion.PY38, TargetVersion.PY39]),
|
(">3.7,<3.10", [TargetVersion.PY38, TargetVersion.PY39]),
|
||||||
(">3.7,!=3.8,!=3.9", [TargetVersion.PY310, TargetVersion.PY311]),
|
(
|
||||||
|
">3.7,!=3.8,!=3.9",
|
||||||
|
[TargetVersion.PY310, TargetVersion.PY311, TargetVersion.PY312],
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"> 3.9.4, != 3.10.3",
|
"> 3.9.4, != 3.10.3",
|
||||||
[TargetVersion.PY39, TargetVersion.PY310, TargetVersion.PY311],
|
[
|
||||||
|
TargetVersion.PY39,
|
||||||
|
TargetVersion.PY310,
|
||||||
|
TargetVersion.PY311,
|
||||||
|
TargetVersion.PY312,
|
||||||
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"!=3.3,!=3.4",
|
"!=3.3,!=3.4",
|
||||||
@ -1552,6 +1572,7 @@ def test_infer_target_version(self) -> None:
|
|||||||
TargetVersion.PY39,
|
TargetVersion.PY39,
|
||||||
TargetVersion.PY310,
|
TargetVersion.PY310,
|
||||||
TargetVersion.PY311,
|
TargetVersion.PY311,
|
||||||
|
TargetVersion.PY312,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -1566,6 +1587,7 @@ def test_infer_target_version(self) -> None:
|
|||||||
TargetVersion.PY39,
|
TargetVersion.PY39,
|
||||||
TargetVersion.PY310,
|
TargetVersion.PY310,
|
||||||
TargetVersion.PY311,
|
TargetVersion.PY311,
|
||||||
|
TargetVersion.PY312,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
("==3.8.*", [TargetVersion.PY38]),
|
("==3.8.*", [TargetVersion.PY38]),
|
||||||
|
@ -134,6 +134,13 @@ def test_python_311(filename: str) -> None:
|
|||||||
assert_format(source, expected, mode, minimum_version=(3, 11))
|
assert_format(source, expected, mode, minimum_version=(3, 11))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("filename", all_data_cases("py_312"))
|
||||||
|
def test_python_312(filename: str) -> None:
|
||||||
|
source, expected = read_data("py_312", filename)
|
||||||
|
mode = black.Mode(target_versions={black.TargetVersion.PY312})
|
||||||
|
assert_format(source, expected, mode, minimum_version=(3, 12))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("filename", all_data_cases("fast"))
|
@pytest.mark.parametrize("filename", all_data_cases("fast"))
|
||||||
def test_fast_cases(filename: str) -> None:
|
def test_fast_cases(filename: str) -> None:
|
||||||
source, expected = read_data("fast", filename)
|
source, expected = read_data("fast", filename)
|
||||||
|
Loading…
Reference in New Issue
Block a user