From 3a5f01a68c37f31ab5f74c0c5547b9b2fc52b1ca Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller Date: Mon, 20 Jan 2025 21:57:19 -0300 Subject: [PATCH 01/11] Improve function definition wrapping when containing generic type declarations. (#4071) --- CHANGES.md | 2 + docs/the_black_code_style/future_style.md | 2 + src/black/linegen.py | 38 +++++++ src/black/mode.py | 1 + src/black/resources/black.schema.json | 3 +- tests/data/cases/generics_wrapping.py | 99 +++++++++++++++++++ tests/data/cases/new_type_param_defaults.py | 61 ++++++++++++ ...aults.py => stable_type_param_defaults.py} | 0 tests/test_black.py | 2 +- 9 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 tests/data/cases/generics_wrapping.py create mode 100644 tests/data/cases/new_type_param_defaults.py rename tests/data/cases/{type_param_defaults.py => stable_type_param_defaults.py} (100%) diff --git a/CHANGES.md b/CHANGES.md index 416aabcdf13..1cc4ad2c707 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,8 @@ - Collapse multiple empty lines after an import into one (#4489) - Prevent `string_processing` and `wrap_long_dict_values_in_parens` from removing parentheses around long dictionary values (#4377) +- Improve the way black wraps generic type definitions in function declarations, + so it will prioritize wrapping parameters instead of the generic types. (#4553) ### Configuration diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index bc3e233ed3d..8fc2987b2b6 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -43,6 +43,8 @@ Currently, the following features are included in the preview style: cases) - `always_one_newline_after_import`: Always force one blank line after import statements, except when the line after the import is a comment or an import statement +- `generic_type_def_wrapping`: Improves the wrapping on function definitions that + containain generic type definitions, such as `def func[T](a: T, b: T):`. (labels/unstable-features)= diff --git a/src/black/linegen.py b/src/black/linegen.py index 90ca5da8587..1405d9ad260 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -787,6 +787,7 @@ def left_hand_split( Prefer RHS otherwise. This is why this function is not symmetrical with :func:`right_hand_split` which also handles optional parentheses. """ + tail_leaves: list[Leaf] = [] body_leaves: list[Leaf] = [] head_leaves: list[Leaf] = [] @@ -805,6 +806,14 @@ def left_hand_split( current_leaves.append(leaf) if current_leaves is head_leaves: if leaf.type in OPENING_BRACKETS: + if ( + Preview.generic_type_def_wrapping in mode + and leaf.type + == token.LSQB # '[' indicates a generic type declaration + and not _should_generic_type_def_in_func_be_splitted(leaf, mode) + ): + continue + matching_bracket = leaf current_leaves = body_leaves if not matching_bracket or not tail_leaves: @@ -825,6 +834,35 @@ def left_hand_split( yield result +def _should_generic_type_def_in_func_be_splitted( + opening_square_bracket: Leaf, mode: Mode +) -> bool: + """ + Receives the leaf of the opening square bracket that starts + the definition of a generic type in a function signature + """ + + # Determine the length of the function definition from its start to ']' + func_def_length = opening_square_bracket.column + current_node: Union[Node, Leaf] = opening_square_bracket + + # Add the length of tokens until the closing bracket is reached + while current_node.next_sibling is not None and current_node.type != token.RSQB: + current_node = current_node.next_sibling + func_def_length += len(str(current_node)) + + # Check if generic types should be split + generic_types_should_be_splitted = ( + func_def_length >= mode.line_length - 3 # Exceeds line length + or ( + current_node.prev_sibling is not None + and current_node.prev_sibling.type == token.COMMA # Trailing comma rule + ) + ) + + return generic_types_should_be_splitted + + def right_hand_split( line: Line, mode: Mode, diff --git a/src/black/mode.py b/src/black/mode.py index 96f72cc2ded..e6cba0e0ebb 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -215,6 +215,7 @@ class Preview(Enum): remove_lone_list_item_parens = auto() pep646_typed_star_arg_type_var_tuple = auto() always_one_newline_after_import = auto() + generic_type_def_wrapping = auto() UNSTABLE_FEATURES: set[Preview] = { diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index f1d471e7616..7174687d6f4 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -93,7 +93,8 @@ "parens_for_long_if_clauses_in_case_block", "remove_lone_list_item_parens", "pep646_typed_star_arg_type_var_tuple", - "always_one_newline_after_import" + "always_one_newline_after_import", + "generic_type_def_wrapping" ] }, "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/generics_wrapping.py b/tests/data/cases/generics_wrapping.py new file mode 100644 index 00000000000..baf14d4c6e2 --- /dev/null +++ b/tests/data/cases/generics_wrapping.py @@ -0,0 +1,99 @@ +# flags: --minimum-version=3.12 --preview +def func[T](a: T, b: T,) -> T: + return a + + +def with_magic_trailling_comma[ + T, + B, +](a: T, b: T,) -> T: + return a + + +def without_magic_trailling_comma[ + T, + B +](a: T, b: T,) -> T: + return a + + +def func[ + T +](a: T, b: T,) -> T: + return a + + +def something_something_function[ + T: Model +](param: list[int], other_param: type[T], *, some_other_param: bool = True) -> QuerySet[ + T +]: + pass + + +def func[A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, LIKE_THIS, AND_THIS, ANOTHER_ONE, AND_YET_ANOTHER_ONE: ThisOneHasTyping](a: T, b: T, c: T, d: T, e: T, f: T, g: T, h: T, i: T, j: T, k: T, l: T, m: T, n: T, o: T, p: T) -> T: + return a + + +# output + + +def func[T]( + a: T, + b: T, +) -> T: + return a + + +def with_magic_trailling_comma[ + T, + B, +](a: T, b: T,) -> T: + return a + + +def without_magic_trailling_comma[T, B]( + a: T, + b: T, +) -> T: + return a + + +def func[T]( + a: T, + b: T, +) -> T: + return a + + +def something_something_function[T: Model]( + param: list[int], other_param: type[T], *, some_other_param: bool = True +) -> QuerySet[T]: + pass + + +def func[ + A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, + LIKE_THIS, + AND_THIS, + ANOTHER_ONE, + AND_YET_ANOTHER_ONE: ThisOneHasTyping, +]( + a: T, + b: T, + c: T, + d: T, + e: T, + f: T, + g: T, + h: T, + i: T, + j: T, + k: T, + l: T, + m: T, + n: T, + o: T, + p: T, +) -> T: + return a diff --git a/tests/data/cases/new_type_param_defaults.py b/tests/data/cases/new_type_param_defaults.py new file mode 100644 index 00000000000..5e992b157cc --- /dev/null +++ b/tests/data/cases/new_type_param_defaults.py @@ -0,0 +1,61 @@ +# flags: --minimum-version=3.13 --preview + +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 trailing_comma1[T=int,](a: str): + pass + +def trailing_comma2[T=int](a: str,): + pass + +# output + +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 trailing_comma1[ + T = int, +](a: str): + pass + + +def trailing_comma2[T = int]( + a: str, +): + pass diff --git a/tests/data/cases/type_param_defaults.py b/tests/data/cases/stable_type_param_defaults.py similarity index 100% rename from tests/data/cases/type_param_defaults.py rename to tests/data/cases/stable_type_param_defaults.py diff --git a/tests/test_black.py b/tests/test_black.py index 98d8ff886d7..2151d0726b8 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -253,7 +253,7 @@ def test_pep_695_version_detection(self) -> None: self.assertIn(black.TargetVersion.PY312, versions) def test_pep_696_version_detection(self) -> None: - source, _ = read_data("cases", "type_param_defaults") + source, _ = read_data("cases", "stable_type_param_defaults") samples = [ source, "type X[T=int] = float", From d59d58d711927e8258f3973b769c5f3767542fbd Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Jan 2025 19:37:11 -0800 Subject: [PATCH 02/11] undo rename --- .../{stable_type_param_defaults.py => type_param_defaults.py} | 0 tests/test_black.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/data/cases/{stable_type_param_defaults.py => type_param_defaults.py} (100%) diff --git a/tests/data/cases/stable_type_param_defaults.py b/tests/data/cases/type_param_defaults.py similarity index 100% rename from tests/data/cases/stable_type_param_defaults.py rename to tests/data/cases/type_param_defaults.py diff --git a/tests/test_black.py b/tests/test_black.py index 2151d0726b8..98d8ff886d7 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -253,7 +253,7 @@ def test_pep_695_version_detection(self) -> None: self.assertIn(black.TargetVersion.PY312, versions) def test_pep_696_version_detection(self) -> None: - source, _ = read_data("cases", "stable_type_param_defaults") + source, _ = read_data("cases", "type_param_defaults") samples = [ source, "type X[T=int] = float", From 22d78217632a32cecb8a6a1d06fcc9e1a40ad22d Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 23 Jan 2025 19:36:04 -0800 Subject: [PATCH 03/11] cosmetic fixes --- src/black/linegen.py | 8 ++++---- tests/data/cases/generics_wrapping.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 1405d9ad260..b5d26d06706 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -808,9 +808,9 @@ def left_hand_split( if leaf.type in OPENING_BRACKETS: if ( Preview.generic_type_def_wrapping in mode - and leaf.type - == token.LSQB # '[' indicates a generic type declaration - and not _should_generic_type_def_in_func_be_splitted(leaf, mode) + # '[' indicates a generic type declaration + and leaf.type == token.LSQB + and not _should_split_generic_type_def(leaf, mode) ): continue @@ -834,7 +834,7 @@ def left_hand_split( yield result -def _should_generic_type_def_in_func_be_splitted( +def _should_split_generic_type_def( opening_square_bracket: Leaf, mode: Mode ) -> bool: """ diff --git a/tests/data/cases/generics_wrapping.py b/tests/data/cases/generics_wrapping.py index baf14d4c6e2..2b22edac856 100644 --- a/tests/data/cases/generics_wrapping.py +++ b/tests/data/cases/generics_wrapping.py @@ -3,14 +3,14 @@ def func[T](a: T, b: T,) -> T: return a -def with_magic_trailling_comma[ +def with_magic_trailing_comma[ T, B, ](a: T, b: T,) -> T: return a -def without_magic_trailling_comma[ +def without_magic_trailing_comma[ T, B ](a: T, b: T,) -> T: @@ -45,14 +45,14 @@ def func[T]( return a -def with_magic_trailling_comma[ +def with_magic_trailing_comma[ T, B, ](a: T, b: T,) -> T: return a -def without_magic_trailling_comma[T, B]( +def without_magic_trailing_comma[T, B]( a: T, b: T, ) -> T: From fb1d0407d819cbd55eb42c46ff0a2eaf51c9454f Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 23 Jan 2025 19:49:51 -0800 Subject: [PATCH 04/11] more test cases --- tests/data/cases/generics_wrapping.py | 164 ++++++++++++++++++++++++-- 1 file changed, 154 insertions(+), 10 deletions(-) diff --git a/tests/data/cases/generics_wrapping.py b/tests/data/cases/generics_wrapping.py index 2b22edac856..01ecff3b640 100644 --- a/tests/data/cases/generics_wrapping.py +++ b/tests/data/cases/generics_wrapping.py @@ -1,27 +1,101 @@ # flags: --minimum-version=3.12 --preview -def func[T](a: T, b: T,) -> T: +def plain[T, B](a: T, b: T) -> T: + return a + +def arg_magic[T, B](a: T, b: T,) -> T: + return a + +def type_param_magic[T, B,](a: T, b: T) -> T: + return a + +def both_magic[T, B,](a: T, b: T,) -> T: return a -def with_magic_trailing_comma[ +def plain_multiline[ + T, + B +]( + a: T, + b: T +) -> T: + return a + +def arg_magic_multiline[ + T, + B +]( + a: T, + b: T, +) -> T: + return a + +def type_param_magic_multiline[ T, B, -](a: T, b: T,) -> T: +]( + a: T, + b: T +) -> T: + return a + +def both_magic_multiline[ + T, + B, +]( + a: T, + b: T, +) -> T: return a -def without_magic_trailing_comma[ +def plain_mixed1[ + T, + B +](a: T, b: T) -> T: + return a + +def plain_mixed2[T, B]( + a: T, + b: T +) -> T: + return a + +def arg_magic_mixed1[ T, B ](a: T, b: T,) -> T: return a +def arg_magic_mixed2[T, B]( + a: T, + b: T, +) -> T: + return a -def func[ - T +def type_param_magic_mixed1[ + T, + B, +](a: T, b: T) -> T: + return a + +def type_param_magic_mixed2[T, B,]( + a: T, + b: T +) -> T: + return a + +def both_magic_mixed1[ + T, + B, ](a: T, b: T,) -> T: return a +def both_magic_mixed2[T, B,]( + a: T, + b: T, +) -> T: + return a def something_something_function[ T: Model @@ -36,36 +110,106 @@ def func[A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, LIKE_THIS, AND_THIS, ANOTH # output +def plain[T, B](a: T, b: T) -> T: + return a -def func[T]( +def arg_magic[T, B]( a: T, b: T, ) -> T: return a -def with_magic_trailing_comma[ +def type_param_magic[ + T, + B, +](a: T, b: T) -> T: + return a + + +def both_magic[ T, B, ](a: T, b: T,) -> T: return a -def without_magic_trailing_comma[T, B]( +def plain_multiline[T, B](a: T, b: T) -> T: + return a + + +def arg_magic_multiline[T, B]( a: T, b: T, ) -> T: return a -def func[T]( +def type_param_magic_multiline[ + T, + B, +](a: T, b: T) -> T: + return a + + +def both_magic_multiline[ + T, + B, +](a: T, b: T,) -> T: + return a + + +def plain_mixed1[T, B](a: T, b: T) -> T: + return a + + +def plain_mixed2[T, B](a: T, b: T) -> T: + return a + + +def arg_magic_mixed1[T, B]( a: T, b: T, ) -> T: return a +def arg_magic_mixed2[T, B]( + a: T, + b: T, +) -> T: + return a + + +def type_param_magic_mixed1[ + T, + B, +](a: T, b: T) -> T: + return a + + +def type_param_magic_mixed2[ + T, + B, +](a: T, b: T) -> T: + return a + + +def both_magic_mixed1[ + T, + B, +](a: T, b: T,) -> T: + return a + + +def both_magic_mixed2[ + T, + B, +](a: T, b: T,) -> T: + return a + + def something_something_function[T: Model]( param: list[int], other_param: type[T], *, some_other_param: bool = True ) -> QuerySet[T]: From 23a37da4d096864a59ff411b2ebd31becdf8a650 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 23 Jan 2025 19:54:28 -0800 Subject: [PATCH 05/11] . --- .../skip_magic_trailing_comma_generic_wrap.py | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 tests/data/cases/skip_magic_trailing_comma_generic_wrap.py diff --git a/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py b/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py new file mode 100644 index 00000000000..3d0d07b771b --- /dev/null +++ b/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py @@ -0,0 +1,207 @@ +# flags: --minimum-version=3.12 --skip-magic-trailing-comma --preview +def plain[T, B](a: T, b: T) -> T: + return a + +def arg_magic[T, B](a: T, b: T,) -> T: + return a + +def type_param_magic[T, B,](a: T, b: T) -> T: + return a + +def both_magic[T, B,](a: T, b: T,) -> T: + return a + + +def plain_multiline[ + T, + B +]( + a: T, + b: T +) -> T: + return a + +def arg_magic_multiline[ + T, + B +]( + a: T, + b: T, +) -> T: + return a + +def type_param_magic_multiline[ + T, + B, +]( + a: T, + b: T +) -> T: + return a + +def both_magic_multiline[ + T, + B, +]( + a: T, + b: T, +) -> T: + return a + + +def plain_mixed1[ + T, + B +](a: T, b: T) -> T: + return a + +def plain_mixed2[T, B]( + a: T, + b: T +) -> T: + return a + +def arg_magic_mixed1[ + T, + B +](a: T, b: T,) -> T: + return a + +def arg_magic_mixed2[T, B]( + a: T, + b: T, +) -> T: + return a + +def type_param_magic_mixed1[ + T, + B, +](a: T, b: T) -> T: + return a + +def type_param_magic_mixed2[T, B,]( + a: T, + b: T +) -> T: + return a + +def both_magic_mixed1[ + T, + B, +](a: T, b: T,) -> T: + return a + +def both_magic_mixed2[T, B,]( + a: T, + b: T, +) -> T: + return a + +def something_something_function[ + T: Model +](param: list[int], other_param: type[T], *, some_other_param: bool = True) -> QuerySet[ + T +]: + pass + + +def func[A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, LIKE_THIS, AND_THIS, ANOTHER_ONE, AND_YET_ANOTHER_ONE: ThisOneHasTyping](a: T, b: T, c: T, d: T, e: T, f: T, g: T, h: T, i: T, j: T, k: T, l: T, m: T, n: T, o: T, p: T) -> T: + return a + + +# output +def plain[T, B](a: T, b: T) -> T: + return a + + +def arg_magic[T, B](a: T, b: T) -> T: + return a + + +def type_param_magic[T, B](a: T, b: T) -> T: + return a + + +def both_magic[T, B](a: T, b: T) -> T: + return a + + +def plain_multiline[T, B](a: T, b: T) -> T: + return a + + +def arg_magic_multiline[T, B](a: T, b: T) -> T: + return a + + +def type_param_magic_multiline[T, B](a: T, b: T) -> T: + return a + + +def both_magic_multiline[T, B](a: T, b: T) -> T: + return a + + +def plain_mixed1[T, B](a: T, b: T) -> T: + return a + + +def plain_mixed2[T, B](a: T, b: T) -> T: + return a + + +def arg_magic_mixed1[T, B](a: T, b: T) -> T: + return a + + +def arg_magic_mixed2[T, B](a: T, b: T) -> T: + return a + + +def type_param_magic_mixed1[T, B](a: T, b: T) -> T: + return a + + +def type_param_magic_mixed2[T, B](a: T, b: T) -> T: + return a + + +def both_magic_mixed1[T, B](a: T, b: T) -> T: + return a + + +def both_magic_mixed2[T, B](a: T, b: T) -> T: + return a + + +def something_something_function[T: Model]( + param: list[int], other_param: type[T], *, some_other_param: bool = True +) -> QuerySet[T]: + pass + + +def func[ + A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, + LIKE_THIS, + AND_THIS, + ANOTHER_ONE, + AND_YET_ANOTHER_ONE: ThisOneHasTyping, +]( + a: T, + b: T, + c: T, + d: T, + e: T, + f: T, + g: T, + h: T, + i: T, + j: T, + k: T, + l: T, + m: T, + n: T, + o: T, + p: T, +) -> T: + return a From 7bf7188baa58bfe30aa41bda9d2ffed218e0edd2 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 23 Jan 2025 19:56:28 -0800 Subject: [PATCH 06/11] remove repeated tests --- .../skip_magic_trailing_comma_generic_wrap.py | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py b/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py index 3d0d07b771b..0f5a6382dca 100644 --- a/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py +++ b/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py @@ -97,17 +97,6 @@ def both_magic_mixed2[T, B,]( ) -> T: return a -def something_something_function[ - T: Model -](param: list[int], other_param: type[T], *, some_other_param: bool = True) -> QuerySet[ - T -]: - pass - - -def func[A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, LIKE_THIS, AND_THIS, ANOTHER_ONE, AND_YET_ANOTHER_ONE: ThisOneHasTyping](a: T, b: T, c: T, d: T, e: T, f: T, g: T, h: T, i: T, j: T, k: T, l: T, m: T, n: T, o: T, p: T) -> T: - return a - # output def plain[T, B](a: T, b: T) -> T: @@ -172,36 +161,3 @@ def both_magic_mixed1[T, B](a: T, b: T) -> T: def both_magic_mixed2[T, B](a: T, b: T) -> T: return a - - -def something_something_function[T: Model]( - param: list[int], other_param: type[T], *, some_other_param: bool = True -) -> QuerySet[T]: - pass - - -def func[ - A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, - LIKE_THIS, - AND_THIS, - ANOTHER_ONE, - AND_YET_ANOTHER_ONE: ThisOneHasTyping, -]( - a: T, - b: T, - c: T, - d: T, - e: T, - f: T, - g: T, - h: T, - i: T, - j: T, - k: T, - l: T, - m: T, - n: T, - o: T, - p: T, -) -> T: - return a From f9eacaaab7c36d629457ab2495afe16e7d5d8f50 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 23 Jan 2025 21:16:56 -0800 Subject: [PATCH 07/11] add failing test case --- tests/data/cases/generics_wrapping.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/data/cases/generics_wrapping.py b/tests/data/cases/generics_wrapping.py index 01ecff3b640..ea36e90cb40 100644 --- a/tests/data/cases/generics_wrapping.py +++ b/tests/data/cases/generics_wrapping.py @@ -109,6 +109,13 @@ def func[A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, LIKE_THIS, AND_THIS, ANOTH return a +def with_random_comments[ + Z + # bye +](): + return a + + # output def plain[T, B](a: T, b: T) -> T: return a From 642592e4d444ceca8f2950ab0c70cc73a95cfd47 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 23 Jan 2025 21:20:55 -0800 Subject: [PATCH 08/11] fix crash, respect magic trailing comma --- src/black/linegen.py | 85 +++++++++------------------ tests/data/cases/generics_wrapping.py | 43 +++++++++++--- tests/util.py | 1 + 3 files changed, 64 insertions(+), 65 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index b5d26d06706..300bd70fb81 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -788,34 +788,34 @@ def left_hand_split( :func:`right_hand_split` which also handles optional parentheses. """ - tail_leaves: list[Leaf] = [] - body_leaves: list[Leaf] = [] - head_leaves: list[Leaf] = [] - current_leaves = head_leaves - matching_bracket: Optional[Leaf] = None - for leaf in line.leaves: - if ( - current_leaves is body_leaves - and leaf.type in CLOSING_BRACKETS - and leaf.opening_bracket is matching_bracket - and isinstance(matching_bracket, Leaf) - ): - ensure_visible(leaf) - ensure_visible(matching_bracket) - current_leaves = tail_leaves if body_leaves else head_leaves - current_leaves.append(leaf) - if current_leaves is head_leaves: - if leaf.type in OPENING_BRACKETS: - if ( - Preview.generic_type_def_wrapping in mode - # '[' indicates a generic type declaration - and leaf.type == token.LSQB - and not _should_split_generic_type_def(leaf, mode) - ): - continue - - matching_bracket = leaf - current_leaves = body_leaves + if Preview.generic_type_def_wrapping in mode: + leaf_type_sets = [[token.LPAR], [token.LSQB]] + else: + leaf_type_sets = [OPENING_BRACKETS] + + for leaf_types in leaf_type_sets: + tail_leaves: list[Leaf] = [] + body_leaves: list[Leaf] = [] + head_leaves: list[Leaf] = [] + current_leaves = head_leaves + matching_bracket: Optional[Leaf] = None + for leaf in line.leaves: + if ( + current_leaves is body_leaves + and leaf.type in CLOSING_BRACKETS + and leaf.opening_bracket is matching_bracket + and isinstance(matching_bracket, Leaf) + ): + ensure_visible(leaf) + ensure_visible(matching_bracket) + current_leaves = tail_leaves if body_leaves else head_leaves + current_leaves.append(leaf) + if current_leaves is head_leaves: + if leaf.type in leaf_types: + matching_bracket = leaf + current_leaves = body_leaves + if matching_bracket and tail_leaves: + break if not matching_bracket or not tail_leaves: raise CannotSplit("No brackets found") @@ -834,35 +834,6 @@ def left_hand_split( yield result -def _should_split_generic_type_def( - opening_square_bracket: Leaf, mode: Mode -) -> bool: - """ - Receives the leaf of the opening square bracket that starts - the definition of a generic type in a function signature - """ - - # Determine the length of the function definition from its start to ']' - func_def_length = opening_square_bracket.column - current_node: Union[Node, Leaf] = opening_square_bracket - - # Add the length of tokens until the closing bracket is reached - while current_node.next_sibling is not None and current_node.type != token.RSQB: - current_node = current_node.next_sibling - func_def_length += len(str(current_node)) - - # Check if generic types should be split - generic_types_should_be_splitted = ( - func_def_length >= mode.line_length - 3 # Exceeds line length - or ( - current_node.prev_sibling is not None - and current_node.prev_sibling.type == token.COMMA # Trailing comma rule - ) - ) - - return generic_types_should_be_splitted - - def right_hand_split( line: Line, mode: Mode, diff --git a/tests/data/cases/generics_wrapping.py b/tests/data/cases/generics_wrapping.py index ea36e90cb40..06ca50cf853 100644 --- a/tests/data/cases/generics_wrapping.py +++ b/tests/data/cases/generics_wrapping.py @@ -131,14 +131,19 @@ def arg_magic[T, B]( def type_param_magic[ T, B, -](a: T, b: T) -> T: +]( + a: T, b: T +) -> T: return a def both_magic[ T, B, -](a: T, b: T,) -> T: +]( + a: T, + b: T, +) -> T: return a @@ -156,14 +161,19 @@ def arg_magic_multiline[T, B]( def type_param_magic_multiline[ T, B, -](a: T, b: T) -> T: +]( + a: T, b: T +) -> T: return a def both_magic_multiline[ T, B, -](a: T, b: T,) -> T: +]( + a: T, + b: T, +) -> T: return a @@ -192,28 +202,38 @@ def arg_magic_mixed2[T, B]( def type_param_magic_mixed1[ T, B, -](a: T, b: T) -> T: +]( + a: T, b: T +) -> T: return a def type_param_magic_mixed2[ T, B, -](a: T, b: T) -> T: +]( + a: T, b: T +) -> T: return a def both_magic_mixed1[ T, B, -](a: T, b: T,) -> T: +]( + a: T, + b: T, +) -> T: return a def both_magic_mixed2[ T, B, -](a: T, b: T,) -> T: +]( + a: T, + b: T, +) -> T: return a @@ -248,3 +268,10 @@ def func[ p: T, ) -> T: return a + + +def with_random_comments[ + Z + # bye +](): + return a diff --git a/tests/util.py b/tests/util.py index 5384af9b8a5..fcd2e4ba5ce 100644 --- a/tests/util.py +++ b/tests/util.py @@ -83,6 +83,7 @@ def _assert_format_equal(expected: str, actual: str) -> None: if actual != expected: out(diff(expected, actual, "expected", "actual")) + print(actual) assert actual == expected From 73c4c687f770dd985157720fab955e8ffa8e2880 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 23 Jan 2025 21:24:03 -0800 Subject: [PATCH 09/11] fix ci --- src/black/linegen.py | 1 + tests/util.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 300bd70fb81..47bb812a7ac 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -788,6 +788,7 @@ def left_hand_split( :func:`right_hand_split` which also handles optional parentheses. """ + leaf_type_sets: list[Collection[int]] if Preview.generic_type_def_wrapping in mode: leaf_type_sets = [[token.LPAR], [token.LSQB]] else: diff --git a/tests/util.py b/tests/util.py index fcd2e4ba5ce..5384af9b8a5 100644 --- a/tests/util.py +++ b/tests/util.py @@ -83,7 +83,6 @@ def _assert_format_equal(expected: str, actual: str) -> None: if actual != expected: out(diff(expected, actual, "expected", "actual")) - print(actual) assert actual == expected From 6147cf734856fff87d90487239164327c598bdaf Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 Jan 2025 18:08:54 -0800 Subject: [PATCH 10/11] comment comment comment --- tests/data/cases/generics_wrapping.py | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/data/cases/generics_wrapping.py b/tests/data/cases/generics_wrapping.py index 06ca50cf853..51ed46effcf 100644 --- a/tests/data/cases/generics_wrapping.py +++ b/tests/data/cases/generics_wrapping.py @@ -116,6 +116,24 @@ def with_random_comments[ return a +def func[ + T, # comment + U # comment + , + Z: # comment + int +](): pass + + +def func[ + T, # comment but it's long so it doesn't just move to the end of the line + U # comment comment comm comm ent ent + , + Z: # comment ent ent comm comm comment + int +](): pass + + # output def plain[T, B](a: T, b: T) -> T: return a @@ -275,3 +293,15 @@ def with_random_comments[ # bye ](): return a + + +def func[T, U, Z: int](): # comment # comment # comment + pass + + +def func[ + T, # comment but it's long so it doesn't just move to the end of the line + U, # comment comment comm comm ent ent + Z: int, # comment ent ent comm comm comment +](): + pass From 19787ae41a569aa080d33a09d072dbc63064fbc6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 24 Jan 2025 18:15:38 -0800 Subject: [PATCH 11/11] straight to stable --- CHANGES.md | 4 +- docs/the_black_code_style/future_style.md | 2 - src/black/linegen.py | 11 +--- src/black/mode.py | 1 - src/black/resources/black.schema.json | 3 +- tests/data/cases/generics_wrapping.py | 2 +- tests/data/cases/new_type_param_defaults.py | 61 ------------------- .../skip_magic_trailing_comma_generic_wrap.py | 2 +- tests/data/cases/type_param_defaults.py | 22 ++++--- 9 files changed, 19 insertions(+), 89 deletions(-) delete mode 100644 tests/data/cases/new_type_param_defaults.py diff --git a/CHANGES.md b/CHANGES.md index 8bac3c392ab..9c0770110e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,8 @@ the following changes: The following changes were not in any previous release: - Remove parentheses around sole list items (#4312) +- Generic function definitions are now formatted more elegantly: parameters are + split over multiple lines first instead of type parameter definitions (#4553) ### Stable style @@ -41,8 +43,6 @@ The following changes were not in any previous release: - Collapse multiple empty lines after an import into one (#4489) - Prevent `string_processing` and `wrap_long_dict_values_in_parens` from removing parentheses around long dictionary values (#4377) -- Improve the way black wraps generic type definitions in function declarations, - so it will prioritize wrapping parameters instead of the generic types. (#4553) ### Configuration diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 770ab14c16a..480834c42e1 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -22,8 +22,6 @@ Currently, the following features are included in the preview style: - `always_one_newline_after_import`: Always force one blank line after import statements, except when the line after the import is a comment or an import statement -- `generic_type_def_wrapping`: Improves the wrapping on function definitions that - containain generic type definitions, such as `def func[T](a: T, b: T):`. (labels/unstable-features)= diff --git a/src/black/linegen.py b/src/black/linegen.py index 82484c7fc2e..ee65a7a6e40 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -779,14 +779,7 @@ def left_hand_split( Prefer RHS otherwise. This is why this function is not symmetrical with :func:`right_hand_split` which also handles optional parentheses. """ - - leaf_type_sets: list[Collection[int]] - if Preview.generic_type_def_wrapping in mode: - leaf_type_sets = [[token.LPAR], [token.LSQB]] - else: - leaf_type_sets = [OPENING_BRACKETS] - - for leaf_types in leaf_type_sets: + for leaf_type in [token.LPAR, token.LSQB]: tail_leaves: list[Leaf] = [] body_leaves: list[Leaf] = [] head_leaves: list[Leaf] = [] @@ -804,7 +797,7 @@ 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 in leaf_types: + if leaf.type == leaf_type: matching_bracket = leaf current_leaves = body_leaves if matching_bracket and tail_leaves: diff --git a/src/black/mode.py b/src/black/mode.py index a133b53e306..a9135946c8a 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -203,7 +203,6 @@ class Preview(Enum): wrap_long_dict_values_in_parens = auto() multiline_string_handling = auto() always_one_newline_after_import = auto() - generic_type_def_wrapping = auto() UNSTABLE_FEATURES: set[Preview] = { diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index 9785cb01759..b9b61489136 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -83,8 +83,7 @@ "hug_parens_with_braces_and_square_brackets", "wrap_long_dict_values_in_parens", "multiline_string_handling", - "always_one_newline_after_import", - "generic_type_def_wrapping" + "always_one_newline_after_import" ] }, "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/generics_wrapping.py b/tests/data/cases/generics_wrapping.py index 51ed46effcf..734e2a3c752 100644 --- a/tests/data/cases/generics_wrapping.py +++ b/tests/data/cases/generics_wrapping.py @@ -1,4 +1,4 @@ -# flags: --minimum-version=3.12 --preview +# flags: --minimum-version=3.12 def plain[T, B](a: T, b: T) -> T: return a diff --git a/tests/data/cases/new_type_param_defaults.py b/tests/data/cases/new_type_param_defaults.py deleted file mode 100644 index 5e992b157cc..00000000000 --- a/tests/data/cases/new_type_param_defaults.py +++ /dev/null @@ -1,61 +0,0 @@ -# flags: --minimum-version=3.13 --preview - -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 trailing_comma1[T=int,](a: str): - pass - -def trailing_comma2[T=int](a: str,): - pass - -# output - -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 trailing_comma1[ - T = int, -](a: str): - pass - - -def trailing_comma2[T = int]( - a: str, -): - pass diff --git a/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py b/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py index 0f5a6382dca..a833f3df863 100644 --- a/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py +++ b/tests/data/cases/skip_magic_trailing_comma_generic_wrap.py @@ -1,4 +1,4 @@ -# flags: --minimum-version=3.12 --skip-magic-trailing-comma --preview +# flags: --minimum-version=3.12 --skip-magic-trailing-comma def plain[T, B](a: T, b: T) -> T: return a diff --git a/tests/data/cases/type_param_defaults.py b/tests/data/cases/type_param_defaults.py index cd844fe0746..feba64a2c72 100644 --- a/tests/data/cases/type_param_defaults.py +++ b/tests/data/cases/type_param_defaults.py @@ -37,25 +37,27 @@ def trailing_comma2[T=int](a: str,): ] = something_that_is_long -def simple[ - T = something_that_is_long -](short1: int, short2: str, short3: bytes) -> float: +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: +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_comma1[ T = int, -](a: str): +]( + a: str, +): pass -def trailing_comma2[ - T = int -](a: str,): +def trailing_comma2[T = int]( + a: str, +): pass