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 -->
|
||||
|
||||
- Add support for the new PEP 695 syntax in Python 3.12 (#3703)
|
||||
|
||||
### Performance
|
||||
|
||||
<!-- Changes that improve Black's performance. -->
|
||||
|
@ -214,4 +214,6 @@ filterwarnings = [
|
||||
# aiohttp is using deprecated cgi modules - Safe to remove when fixed:
|
||||
# https://github.com/aio-libs/aiohttp/issues/6905
|
||||
'''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)
|
||||
|
||||
elif n.type in (syms.type_stmt, syms.typeparams):
|
||||
features.add(Feature.TYPE_PARAMS)
|
||||
|
||||
return features
|
||||
|
||||
|
||||
|
@ -215,6 +215,18 @@ def visit_stmt(
|
||||
|
||||
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]:
|
||||
if Preview.wrap_long_dict_values_in_parens in self.mode:
|
||||
for i, child in enumerate(node.children):
|
||||
|
@ -30,6 +30,7 @@ class TargetVersion(Enum):
|
||||
PY39 = 9
|
||||
PY310 = 10
|
||||
PY311 = 11
|
||||
PY312 = 12
|
||||
|
||||
|
||||
class Feature(Enum):
|
||||
@ -51,6 +52,7 @@ class Feature(Enum):
|
||||
VARIADIC_GENERICS = 15
|
||||
DEBUG_F_STRINGS = 16
|
||||
PARENTHESIZED_CONTEXT_MANAGERS = 17
|
||||
TYPE_PARAMS = 18
|
||||
FORCE_OPTIONAL_PARENTHESES = 50
|
||||
|
||||
# __future__ flags
|
||||
@ -143,6 +145,25 @@ class Feature(Enum):
|
||||
Feature.EXCEPT_STAR,
|
||||
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
|
||||
eval_input: testlist NEWLINE* ENDMARKER
|
||||
|
||||
typevar: NAME [':' expr]
|
||||
paramspec: '**' NAME
|
||||
typevartuple: '*' NAME
|
||||
typeparam: typevar | paramspec | typevartuple
|
||||
typeparams: '[' typeparam (',' typeparam)* [','] ']'
|
||||
|
||||
decorator: '@' namedexpr_test NEWLINE
|
||||
decorators: decorator+
|
||||
decorated: decorators (classdef | funcdef | async_funcdef)
|
||||
async_funcdef: ASYNC funcdef
|
||||
funcdef: 'def' NAME parameters ['->' test] ':' suite
|
||||
funcdef: 'def' NAME [typeparams] parameters ['->' test] ':' suite
|
||||
parameters: '(' [typedargslist] ')'
|
||||
|
||||
# The following definition for typedarglist is equivalent to this set of rules:
|
||||
@ -74,7 +80,7 @@ vfplist: vfpdef (',' vfpdef)* [',']
|
||||
|
||||
stmt: simple_stmt | compound_stmt
|
||||
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)
|
||||
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
|
||||
('=' (yield_expr|testlist_star_expr))*)
|
||||
@ -105,6 +111,7 @@ dotted_name: NAME ('.' NAME)*
|
||||
global_stmt: ('global' | 'nonlocal') NAME (',' NAME)*
|
||||
exec_stmt: 'exec' expr ['in' 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
|
||||
async_stmt: ASYNC (funcdef | with_stmt | for_stmt)
|
||||
@ -174,7 +181,7 @@ dictsetmaker: ( ((test ':' asexpr_test | '**' expr)
|
||||
((test [':=' test] | star_expr)
|
||||
(comp_for | (',' (test [':=' test] | star_expr))* [','])) )
|
||||
|
||||
classdef: 'class' NAME ['(' [arglist] ')'] ':' suite
|
||||
classdef: 'class' NAME [typeparams] ['(' [arglist] ')'] ':' suite
|
||||
|
||||
arglist: argument (',' argument)* [',']
|
||||
|
||||
|
@ -95,6 +95,7 @@ class _python_symbols(Symbols):
|
||||
old_test: int
|
||||
or_test: int
|
||||
parameters: int
|
||||
paramspec: int
|
||||
pass_stmt: int
|
||||
pattern: int
|
||||
patterns: int
|
||||
@ -126,7 +127,12 @@ class _python_symbols(Symbols):
|
||||
tname_star: int
|
||||
trailer: int
|
||||
try_stmt: int
|
||||
type_stmt: int
|
||||
typedargslist: int
|
||||
typeparam: int
|
||||
typeparams: int
|
||||
typevar: int
|
||||
typevartuple: int
|
||||
varargslist: int
|
||||
vfpdef: 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)
|
||||
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:
|
||||
source, expected = read_data("simple_cases", "expression.py")
|
||||
tmp_file = Path(black.dump_to_file(source))
|
||||
@ -1533,14 +1542,25 @@ def test_infer_target_version(self) -> None:
|
||||
for version, expected in [
|
||||
("3.6", [TargetVersion.PY36]),
|
||||
("3.11.0rc1", [TargetVersion.PY311]),
|
||||
(">=3.10", [TargetVersion.PY310, TargetVersion.PY311]),
|
||||
(">=3.10.6", [TargetVersion.PY310, TargetVersion.PY311]),
|
||||
(">=3.10", [TargetVersion.PY310, TargetVersion.PY311, TargetVersion.PY312]),
|
||||
(
|
||||
">=3.10.6",
|
||||
[TargetVersion.PY310, TargetVersion.PY311, TargetVersion.PY312],
|
||||
),
|
||||
("<3.6", [TargetVersion.PY33, TargetVersion.PY34, TargetVersion.PY35]),
|
||||
(">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",
|
||||
[TargetVersion.PY39, TargetVersion.PY310, TargetVersion.PY311],
|
||||
[
|
||||
TargetVersion.PY39,
|
||||
TargetVersion.PY310,
|
||||
TargetVersion.PY311,
|
||||
TargetVersion.PY312,
|
||||
],
|
||||
),
|
||||
(
|
||||
"!=3.3,!=3.4",
|
||||
@ -1552,6 +1572,7 @@ def test_infer_target_version(self) -> None:
|
||||
TargetVersion.PY39,
|
||||
TargetVersion.PY310,
|
||||
TargetVersion.PY311,
|
||||
TargetVersion.PY312,
|
||||
],
|
||||
),
|
||||
(
|
||||
@ -1566,6 +1587,7 @@ def test_infer_target_version(self) -> None:
|
||||
TargetVersion.PY39,
|
||||
TargetVersion.PY310,
|
||||
TargetVersion.PY311,
|
||||
TargetVersion.PY312,
|
||||
],
|
||||
),
|
||||
("==3.8.*", [TargetVersion.PY38]),
|
||||
|
@ -134,6 +134,13 @@ def test_python_311(filename: str) -> None:
|
||||
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"))
|
||||
def test_fast_cases(filename: str) -> None:
|
||||
source, expected = read_data("fast", filename)
|
||||
|
Loading…
Reference in New Issue
Block a user