From 846f009115fef86d32d5525f3bfa0d4ccf86e905 Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Mon, 6 Oct 2025 00:26:44 +0530 Subject: [PATCH 01/11] fix(3.12_generics_syntax): Fixed formatting of 3.12 generics syntax --- src/black/linegen.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 27c2c92d9b2..eb6937426bf 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -834,7 +834,9 @@ def left_hand_split( current_leaves = tail_leaves if body_leaves else head_leaves current_leaves.append(leaf) if current_leaves is head_leaves: - if leaf.type == leaf_type: + if leaf.type == leaf_type and not ( + leaf_type == token.LPAR and depth > 0 + ): matching_bracket = leaf current_leaves = body_leaves if matching_bracket and tail_leaves: From 884fe92759d5085edca882c5511bccf31d198e53 Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Mon, 6 Oct 2025 01:02:29 +0530 Subject: [PATCH 02/11] chore: add test cases and update CHANGELOG --- CHANGES.md | 2 + tests/data/cases/type_param_defaults.py | 59 +++++++++++++++++++------ 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 98e4f6506ad..c055cea058a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ +- Fix bug where python 3.12 generics syntax split line happens weirdly (#4740) + ### Preview style diff --git a/tests/data/cases/type_param_defaults.py b/tests/data/cases/type_param_defaults.py index f8c24eb7cff..52ea6764a59 100644 --- a/tests/data/cases/type_param_defaults.py +++ b/tests/data/cases/type_param_defaults.py @@ -1,26 +1,54 @@ # flags: --minimum-version=3.13 -type A[T=int] = float -type B[*P=int] = float -type C[*Ts=int] = float -type D[*Ts=*int] = float -type D[something_that_is_very_very_very_long=something_that_is_very_very_very_long] = float -type D[*something_that_is_very_very_very_long=*something_that_is_very_very_very_long] = float -type something_that_is_long[something_that_is_long=something_that_is_long] = something_that_is_long - -def simple[T=something_that_is_long](short1: int, short2: str, short3: bytes) -> float: +type A[T = int] = float +type B[*P = int] = float +type C[*Ts = int] = float +type D[*Ts = *int] = float +type D[ + something_that_is_very_very_very_long = something_that_is_very_very_very_long +] = float +type D[ + *something_that_is_very_very_very_long = *something_that_is_very_very_very_long +] = float +type something_that_is_long[ + something_that_is_long = something_that_is_long +] = something_that_is_long + + +def simple[T = something_that_is_long]( + short1: int, short2: str, short3: bytes +) -> float: + pass + + +def longer[something_that_is_long = something_that_is_long]( + something_that_is_long: something_that_is_long, +) -> something_that_is_long: pass -def longer[something_that_is_long=something_that_is_long](something_that_is_long: something_that_is_long) -> something_that_is_long: + +def trailing_comma1[ + T = int, +]( + a: str, +): pass -def trailing_comma1[T=int,](a: str): + +def trailing_comma2[T = int]( + a: str, +): pass -def trailing_comma2[T=int](a: str,): + +def weird_syntax[T = lambda: 42, **P = lambda: 43, *Ts = lambda: 44](): pass -def weird_syntax[T=lambda: 42, **P=lambda: 43, *Ts=lambda: 44](): pass + +def func1[T: (int, str)]( + a, +): ... + # output @@ -67,3 +95,8 @@ def trailing_comma2[T = int]( def weird_syntax[T = lambda: 42, **P = lambda: 43, *Ts = lambda: 44](): pass + + +def func1[T: (int, str)]( + a, +): ... From afb30842e019cfd251534c71eaf06b2287cc7bdb Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Mon, 6 Oct 2025 01:19:27 +0530 Subject: [PATCH 03/11] revert: Formatted test code files, Im dumb --- tests/data/cases/type_param_defaults.py | 59 ++++++------------------- 1 file changed, 13 insertions(+), 46 deletions(-) diff --git a/tests/data/cases/type_param_defaults.py b/tests/data/cases/type_param_defaults.py index 52ea6764a59..f8c24eb7cff 100644 --- a/tests/data/cases/type_param_defaults.py +++ b/tests/data/cases/type_param_defaults.py @@ -1,54 +1,26 @@ # flags: --minimum-version=3.13 -type A[T = int] = float -type B[*P = int] = float -type C[*Ts = int] = float -type D[*Ts = *int] = float -type D[ - something_that_is_very_very_very_long = something_that_is_very_very_very_long -] = float -type D[ - *something_that_is_very_very_very_long = *something_that_is_very_very_very_long -] = float -type something_that_is_long[ - something_that_is_long = something_that_is_long -] = something_that_is_long - - -def simple[T = something_that_is_long]( - short1: int, short2: str, short3: bytes -) -> float: - pass - - -def longer[something_that_is_long = something_that_is_long]( - something_that_is_long: something_that_is_long, -) -> something_that_is_long: +type A[T=int] = float +type B[*P=int] = float +type C[*Ts=int] = float +type D[*Ts=*int] = float +type D[something_that_is_very_very_very_long=something_that_is_very_very_very_long] = float +type D[*something_that_is_very_very_very_long=*something_that_is_very_very_very_long] = float +type something_that_is_long[something_that_is_long=something_that_is_long] = something_that_is_long + +def simple[T=something_that_is_long](short1: int, short2: str, short3: bytes) -> float: pass - -def trailing_comma1[ - T = int, -]( - a: str, -): +def longer[something_that_is_long=something_that_is_long](something_that_is_long: something_that_is_long) -> something_that_is_long: pass - -def trailing_comma2[T = int]( - a: str, -): +def trailing_comma1[T=int,](a: str): pass - -def weird_syntax[T = lambda: 42, **P = lambda: 43, *Ts = lambda: 44](): +def trailing_comma2[T=int](a: str,): pass - -def func1[T: (int, str)]( - a, -): ... - +def weird_syntax[T=lambda: 42, **P=lambda: 43, *Ts=lambda: 44](): pass # output @@ -95,8 +67,3 @@ def trailing_comma2[T = int]( def weird_syntax[T = lambda: 42, **P = lambda: 43, *Ts = lambda: 44](): pass - - -def func1[T: (int, str)]( - a, -): ... From f613771f3bf7a8517b95ee8daac040682b10f1eb Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Mon, 6 Oct 2025 02:43:57 +0530 Subject: [PATCH 04/11] chore: update with multiple tests --- tests/data/cases/type_params.py | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/data/cases/type_params.py b/tests/data/cases/type_params.py index 124292d6d54..b7061fac24d 100644 --- a/tests/data/cases/type_params.py +++ b/tests/data/cases/type_params.py @@ -17,6 +17,19 @@ def weird_syntax[T: lambda: 42, U: a or b](): pass def name_3[name_0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if aaaaaaaaaaa else name_3](): pass +def f1[T: (int, str)](a,): pass + +def f2[T: (int, str)](a: int, b,): pass + +def g1[T: (int,)](a,): pass + +def g2[T: (int, str, bytes)](a,): pass + +def g3[T: ((int, str), (bytes,))](a,): pass + +def g4[T: (int, (str, bytes))](a,): pass + +def g5[T: ((int,),)](a: int, b,): pass # output @@ -70,3 +83,47 @@ def name_3[ name_0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if aaaaaaaaaaa else name_3 ](): pass + + +def f1[T: (int, str)]( + a, +): + pass + + +def f2[T: (int, str)]( + a: int, + b, +): + pass + + +def g1[T: (int,)]( + a, +): + pass + + +def g2[T: (int, str, bytes)]( + a, +): + pass + + +def g3[T: ((int, str), (bytes,))]( + a, +): + pass + + +def g4[T: (int, (str, bytes))]( + a, +): + pass + + +def g5[T: ((int,),)]( + a: int, + b, +): + pass \ No newline at end of file From 71c02ef6caf2312c33846cbd8ff8299ed1c60f03 Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Mon, 6 Oct 2025 02:44:18 +0530 Subject: [PATCH 05/11] fix: Use PR number instead of issue --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c055cea058a..8ddfb587763 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,7 +17,7 @@ - Fix bug where module docstrings would be treated as normal strings if preceeded by - comments (#4764) + comments (#4777) ### Configuration From 30737e1c02cb73ebdc680781d87807db38223fef Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:27:45 +0530 Subject: [PATCH 06/11] chore: move fix to preview with proper documentation for stability --- docs/the_black_code_style/future_style.md | 1 + src/black/linegen.py | 5 +- src/black/mode.py | 1 + src/black/resources/black.schema.json | 3 +- tests/data/cases/type_expansion.py | 60 +++++++++++++++++++++++ tests/data/cases/type_params.py | 58 ---------------------- 6 files changed, 67 insertions(+), 61 deletions(-) create mode 100644 tests/data/cases/type_expansion.py diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index c1e88c1cba5..f5651c20d4a 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -37,6 +37,7 @@ Currently, the following features are included in the preview style: normalize file newlines both from and to. - `fix_module_docstring_detection`: Fix module docstrings being treated as normal strings if preceeded by comments. +- `fix_type_expansion_split`: Fix type expansions split in generic functions. (labels/unstable-features)= diff --git a/src/black/linegen.py b/src/black/linegen.py index eb6937426bf..29e3d92e28a 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -834,8 +834,9 @@ def left_hand_split( current_leaves = tail_leaves if body_leaves else head_leaves current_leaves.append(leaf) if current_leaves is head_leaves: - if leaf.type == leaf_type and not ( - leaf_type == token.LPAR and depth > 0 + if leaf.type == leaf_type and ( + Preview.fix_type_expansion_split + and not (leaf_type == token.LPAR and depth > 0) ): matching_bracket = leaf current_leaves = body_leaves diff --git a/src/black/mode.py b/src/black/mode.py index 9927f73c4a7..79dfed41047 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -237,6 +237,7 @@ class Preview(Enum): remove_parens_around_except_types = auto() normalize_cr_newlines = auto() fix_module_docstring_detection = auto() + fix_type_expansion_split = auto() UNSTABLE_FEATURES: set[Preview] = { diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index 8ea19908570..9e60db33ebf 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -89,7 +89,8 @@ "wrap_comprehension_in", "remove_parens_around_except_types", "normalize_cr_newlines", - "fix_module_docstring_detection" + "fix_module_docstring_detection", + "fix_type_expansion_split" ] }, "description": "Enable specific features included in the `--unstable` style. Requires `--preview`. No compatibility guarantees are provided on the behavior or existence of any unstable features." diff --git a/tests/data/cases/type_expansion.py b/tests/data/cases/type_expansion.py new file mode 100644 index 00000000000..ee91e794259 --- /dev/null +++ b/tests/data/cases/type_expansion.py @@ -0,0 +1,60 @@ +# flags: --preview + +def f1[T: (int, str)](a,): pass + +def f2[T: (int, str)](a: int, b,): pass + +def g1[T: (int,)](a,): pass + +def g2[T: (int, str, bytes)](a,): pass + +def g3[T: ((int, str), (bytes,))](a,): pass + +def g4[T: (int, (str, bytes))](a,): pass + +def g5[T: ((int,),)](a: int, b,): pass + +# output + +def f1[T: (int, str)]( + a, +): + pass + + +def f2[T: (int, str)]( + a: int, + b, +): + pass + + +def g1[T: (int,)]( + a, +): + pass + + +def g2[T: (int, str, bytes)]( + a, +): + pass + + +def g3[T: ((int, str), (bytes,))]( + a, +): + pass + + +def g4[T: (int, (str, bytes))]( + a, +): + pass + + +def g5[T: ((int,),)]( + a: int, + b, +): + pass diff --git a/tests/data/cases/type_params.py b/tests/data/cases/type_params.py index b7061fac24d..d1c2d8ee782 100644 --- a/tests/data/cases/type_params.py +++ b/tests/data/cases/type_params.py @@ -16,20 +16,6 @@ def magic[Trailing, Comma,](): pass def weird_syntax[T: lambda: 42, U: a or b](): pass def name_3[name_0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if aaaaaaaaaaa else name_3](): pass - -def f1[T: (int, str)](a,): pass - -def f2[T: (int, str)](a: int, b,): pass - -def g1[T: (int,)](a,): pass - -def g2[T: (int, str, bytes)](a,): pass - -def g3[T: ((int, str), (bytes,))](a,): pass - -def g4[T: (int, (str, bytes))](a,): pass - -def g5[T: ((int,),)](a: int, b,): pass # output @@ -83,47 +69,3 @@ def name_3[ name_0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if aaaaaaaaaaa else name_3 ](): pass - - -def f1[T: (int, str)]( - a, -): - pass - - -def f2[T: (int, str)]( - a: int, - b, -): - pass - - -def g1[T: (int,)]( - a, -): - pass - - -def g2[T: (int, str, bytes)]( - a, -): - pass - - -def g3[T: ((int, str), (bytes,))]( - a, -): - pass - - -def g4[T: (int, (str, bytes))]( - a, -): - pass - - -def g5[T: ((int,),)]( - a: int, - b, -): - pass \ No newline at end of file From caffdbc9f3f9308050c1dd90bdaab7d213785204 Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:11:17 +0530 Subject: [PATCH 07/11] chore: update linegen to check against mode Co-authored-by: GiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> --- src/black/linegen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 29e3d92e28a..09197e674e7 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -835,8 +835,8 @@ def left_hand_split( current_leaves.append(leaf) if current_leaves is head_leaves: if leaf.type == leaf_type and ( - Preview.fix_type_expansion_split - and not (leaf_type == token.LPAR and depth > 0) + Preview.fix_type_expansion_split not in mode + or not (leaf_type == token.LPAR and depth > 0) ): matching_bracket = leaf current_leaves = body_leaves From b993aa3e27ae3591aec913965bceb7f80da41007 Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:25:28 +0530 Subject: [PATCH 08/11] chore: update changes.md to move it to preview --- CHANGES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8ddfb587763..d989c1d4c1b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,14 +10,14 @@ -- Fix bug where python 3.12 generics syntax split line happens weirdly (#4740) - ### Preview style +- Fix bug where python 3.12 generics syntax split line happens weirdly (#4740) + - Fix bug where module docstrings would be treated as normal strings if preceeded by - comments (#4777) + comments (#4764) ### Configuration From f9bf68a1f8a0262f9f8976ea216f9f6a77578fca Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:29:26 +0530 Subject: [PATCH 09/11] fix: minimum python version to 3.12 for tests --- tests/data/cases/type_expansion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data/cases/type_expansion.py b/tests/data/cases/type_expansion.py index ee91e794259..6cd7a0b7736 100644 --- a/tests/data/cases/type_expansion.py +++ b/tests/data/cases/type_expansion.py @@ -1,4 +1,4 @@ -# flags: --preview +# flags: --preview --minimum-version=3.12 def f1[T: (int, str)](a,): pass From add41f5bdc2292c7c7034843986530f978d69875 Mon Sep 17 00:00:00 2001 From: GiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:26:00 -0700 Subject: [PATCH 10/11] Fix changelog entry --- CHANGES.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d989c1d4c1b..50bf323f1a3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,10 +14,9 @@ -- Fix bug where python 3.12 generics syntax split line happens weirdly (#4740) - - Fix bug where module docstrings would be treated as normal strings if preceeded by comments (#4764) +- Fix bug where python 3.12 generics syntax split line happens weirdly (#4777) ### Configuration From 9cd4fbe47b110a94b7137b582e74076d389f1080 Mon Sep 17 00:00:00 2001 From: GiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:29:09 -0700 Subject: [PATCH 11/11] Update type_params.py to remove unnecessary edit --- tests/data/cases/type_params.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/data/cases/type_params.py b/tests/data/cases/type_params.py index d1c2d8ee782..124292d6d54 100644 --- a/tests/data/cases/type_params.py +++ b/tests/data/cases/type_params.py @@ -16,6 +16,7 @@ def magic[Trailing, Comma,](): pass def weird_syntax[T: lambda: 42, U: a or b](): pass def name_3[name_0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if aaaaaaaaaaa else name_3](): pass + # output