<!-- 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. -->
# 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'''
]
):
features.add(Feature.VARIADIC_GENERICS)
+ elif n.type in (syms.type_stmt, syms.typeparams):
+ features.add(Feature.TYPE_PARAMS)
+
return features
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):
PY39 = 9
PY310 = 10
PY311 = 11
+ PY312 = 12
class Feature(Enum):
VARIADIC_GENERICS = 15
DEBUG_F_STRINGS = 16
PARENTHESIZED_CONTEXT_MANAGERS = 17
+ TYPE_PARAMS = 18
FORCE_OPTIONAL_PARENTHESES = 50
# __future__ flags
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,
+ },
}
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:
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))*)
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)
((test [':=' test] | star_expr)
(comp_for | (',' (test [':=' test] | star_expr))* [','])) )
-classdef: 'class' NAME ['(' [arglist] ')'] ':' suite
+classdef: 'class' NAME [typeparams] ['(' [arglist] ')'] ':' suite
arglist: argument (',' argument)* [',']
old_test: int
or_test: int
parameters: int
+ paramspec: int
pass_stmt: int
pattern: int
patterns: int
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
--- /dev/null
+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))
--- /dev/null
+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
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))
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",
TargetVersion.PY39,
TargetVersion.PY310,
TargetVersion.PY311,
+ TargetVersion.PY312,
],
),
(
TargetVersion.PY39,
TargetVersion.PY310,
TargetVersion.PY311,
+ TargetVersion.PY312,
],
),
("==3.8.*", [TargetVersion.PY38]),
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)