]> git.madduck.net Git - etc/vim.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Add support for PEP 695 syntax (#3703)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Fri, 2 Jun 2023 01:37:08 +0000 (18:37 -0700)
committerGitHub <noreply@github.com>
Fri, 2 Jun 2023 01:37:08 +0000 (18:37 -0700)
CHANGES.md
pyproject.toml
src/black/__init__.py
src/black/linegen.py
src/black/mode.py
src/blib2to3/Grammar.txt
src/blib2to3/pygram.py
tests/data/py_312/type_aliases.py [new file with mode: 0644]
tests/data/py_312/type_params.py [new file with mode: 0644]
tests/test_black.py
tests/test_format.py

index 762a799a1d10d06c139fe99c724aed0e6f35f0e0..fb3dea8c348fd54677f543a07ceb4d51cb0eb1e9 100644 (file)
@@ -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
 
 <!-- Changes that improve Black's performance. -->
 ### Performance
 
 <!-- Changes that improve Black's performance. -->
index 435626ac8f4dbccc6715aec8d3c0b74de4b4e994..6803a627e9ab8e27a73f91d6cd6d21f9011aaf4a 100644 (file)
@@ -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''',
     # 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'''
 ]
 ]
index 871e9a0d7c8cf9e56477469ca7929346a3511a0e..8a759aa493ae2e0e045d52549d8071b7f0677547 100644 (file)
@@ -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
 
 
index b6b83da26f776fa4810c18f42a1c911bc2eea67e..0091cbb3bd1d3195d3bc06fed11aeac280b987bf 100644 (file)
@@ -215,6 +215,18 @@ class LineGenerator(Visitor[Line]):
 
             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]:
         if Preview.wrap_long_dict_values_in_parens in self.mode:
             for i, child in enumerate(node.children):
     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):
index a5841edb30a791cd17d8ad810542adbd7640e617..1091494afac588032809154fb2c18b100e883cf1 100644 (file)
@@ -30,6 +30,7 @@ class TargetVersion(Enum):
     PY39 = 9
     PY310 = 10
     PY311 = 11
     PY39 = 9
     PY310 = 10
     PY311 = 11
+    PY312 = 12
 
 
 class Feature(Enum):
 
 
 class Feature(Enum):
@@ -51,6 +52,7 @@ class Feature(Enum):
     VARIADIC_GENERICS = 15
     DEBUG_F_STRINGS = 16
     PARENTHESIZED_CONTEXT_MANAGERS = 17
     VARIADIC_GENERICS = 15
     DEBUG_F_STRINGS = 16
     PARENTHESIZED_CONTEXT_MANAGERS = 17
+    TYPE_PARAMS = 18
     FORCE_OPTIONAL_PARENTHESES = 50
 
     # __future__ flags
     FORCE_OPTIONAL_PARENTHESES = 50
 
     # __future__ flags
@@ -143,6 +145,25 @@ VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = {
         Feature.EXCEPT_STAR,
         Feature.VARIADIC_GENERICS,
     },
         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,
+    },
 }
 
 
 }
 
 
index bd8a452a3862476be93bb95776cfe5e15db6f01e..e48e66363fb504f2430d2ec61553e122fbd52ffc 100644 (file)
@@ -12,11 +12,17 @@ file_input: (NEWLINE | stmt)* ENDMARKER
 single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
 eval_input: testlist NEWLINE* 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
 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:
 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
 
 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))*)
              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]
 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)
 
 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))* [','])) )
 
                 ((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)* [',']
 
index 99012cdd9cb30f05369b6046702b73829bed2016..15702e4059e9eb95f06d83a4de5cc2761c84cd42 100644 (file)
@@ -95,6 +95,7 @@ class _python_symbols(Symbols):
     old_test: int
     or_test: int
     parameters: int
     old_test: int
     or_test: int
     parameters: int
+    paramspec: int
     pass_stmt: int
     pattern: int
     patterns: int
     pass_stmt: int
     pattern: int
     patterns: int
@@ -126,7 +127,12 @@ class _python_symbols(Symbols):
     tname_star: int
     trailer: int
     try_stmt: int
     tname_star: int
     trailer: int
     try_stmt: int
+    type_stmt: int
     typedargslist: int
     typedargslist: int
+    typeparam: int
+    typeparams: int
+    typevar: int
+    typevartuple: int
     varargslist: int
     vfpdef: int
     vfplist: int
     varargslist: int
     vfpdef: int
     vfplist: int
diff --git a/tests/data/py_312/type_aliases.py b/tests/data/py_312/type_aliases.py
new file mode 100644 (file)
index 0000000..84e07e5
--- /dev/null
@@ -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))
diff --git a/tests/data/py_312/type_params.py b/tests/data/py_312/type_params.py
new file mode 100644 (file)
index 0000000..5f8ec43
--- /dev/null
@@ -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
index 00de5b745e7842af4a3e95254b3d4e63afe08450..42b0161d1569bdc5f6174d6ffe71be1b015df1ed 100644 (file)
@@ -271,6 +271,15 @@ class BlackTestCase(BlackBaseTestCase):
         versions = black.detect_target_versions(root)
         self.assertIn(black.TargetVersion.PY38, versions)
 
         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))
     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 @@ class BlackTestCase(BlackBaseTestCase):
         for version, expected in [
             ("3.6", [TargetVersion.PY36]),
             ("3.11.0rc1", [TargetVersion.PY311]),
         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.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",
             (
                 "> 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 @@ class BlackTestCase(BlackBaseTestCase):
                     TargetVersion.PY39,
                     TargetVersion.PY310,
                     TargetVersion.PY311,
                     TargetVersion.PY39,
                     TargetVersion.PY310,
                     TargetVersion.PY311,
+                    TargetVersion.PY312,
                 ],
             ),
             (
                 ],
             ),
             (
@@ -1566,6 +1587,7 @@ class BlackTestCase(BlackBaseTestCase):
                     TargetVersion.PY39,
                     TargetVersion.PY310,
                     TargetVersion.PY311,
                     TargetVersion.PY39,
                     TargetVersion.PY310,
                     TargetVersion.PY311,
+                    TargetVersion.PY312,
                 ],
             ),
             ("==3.8.*", [TargetVersion.PY38]),
                 ],
             ),
             ("==3.8.*", [TargetVersion.PY38]),
index 5a7b3bb67627c5f1f8f043174f8abe48e8cd1988..8e0ada99cba0138aa446d535c35fe55c3e895dd9 100644 (file)
@@ -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"))
 def test_fast_cases(filename: str) -> None:
     source, expected = read_data("fast", filename)
 @pytest.mark.parametrize("filename", all_data_cases("fast"))
 def test_fast_cases(filename: str) -> None:
     source, expected = read_data("fast", filename)