From 9a104363ea95033acb130792f97cf8ce35278d0f Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 16 Jul 2023 23:40:23 -0700 Subject: [PATCH 1/9] [ignore] curious for diff shade output --- src/black/linegen.py | 10 +++++++--- src/black/mode.py | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 5ef3bbd1705..507e860190f 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -281,7 +281,9 @@ def visit_match_case(self, node: Node) -> Iterator[Line]: def visit_suite(self, node: Node) -> Iterator[Line]: """Visit a suite.""" - if self.mode.is_pyi and is_stub_suite(node): + if ( + self.mode.is_pyi or Preview.dummy_implementations in self.mode + ) and is_stub_suite(node): yield from self.visit(node.children[2]) else: yield from self.visit_default(node) @@ -296,7 +298,9 @@ def visit_simple_stmt(self, node: Node) -> Iterator[Line]: is_suite_like = node.parent and node.parent.type in STATEMENT if is_suite_like: - if self.mode.is_pyi and is_stub_body(node): + if ( + self.mode.is_pyi or Preview.dummy_implementations in self.mode + ) and is_stub_body(node): yield from self.visit_default(node) else: yield from self.line(+1) @@ -305,7 +309,7 @@ def visit_simple_stmt(self, node: Node) -> Iterator[Line]: else: if ( - not self.mode.is_pyi + not (self.mode.is_pyi or Preview.dummy_implementations in self.mode) or not node.parent or not is_stub_suite(node.parent) ): diff --git a/src/black/mode.py b/src/black/mode.py index 4d979afd84d..282c1669da7 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -182,6 +182,7 @@ class Preview(Enum): skip_magic_trailing_comma_in_subscript = auto() wrap_long_dict_values_in_parens = auto() wrap_multiple_context_managers_in_parens = auto() + dummy_implementations = auto() class Deprecated(UserWarning): From c38a4706c6394bce5eb4fcc6c5abc1176c57a05f Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 22 Jul 2023 12:04:58 -0700 Subject: [PATCH 2/9] empty lines --- src/black/lines.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index 016a489310d..460a9f66763 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -5,6 +5,7 @@ from typing import ( Callable, Dict, + Final, Iterator, List, Optional, @@ -165,6 +166,13 @@ def is_def(self) -> bool: and second_leaf.value == "def" ) + @property + def is_stub_def(self) -> bool: + """Is this line a function definition with a body consisting only of "..."?""" + return self.is_def and self.leaves[-3:] == [ + Leaf(token.DOT, ".") for _ in range(3) + ] + @property def is_class_paren_empty(self) -> bool: """Is this a class with no base classes but using parentheses? @@ -578,6 +586,8 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: first_leaf.prefix = "" else: before = 0 + + user_hint_before: Final = before depth = current_line.depth previous_def = None @@ -624,7 +634,9 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: before = 2 if current_line.is_decorator or current_line.is_def or current_line.is_class: - return self._maybe_empty_lines_for_class_or_def(current_line, before) + return self._maybe_empty_lines_for_class_or_def( + current_line, before, user_hint_before + ) if ( self.previous_line @@ -648,8 +660,8 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: return 0, 0 return before, 0 - def _maybe_empty_lines_for_class_or_def( - self, current_line: Line, before: int + def _maybe_empty_lines_for_class_or_def( # noqa: C901 + self, current_line: Line, before: int, user_hint_before: int ) -> Tuple[int, int]: if not current_line.is_decorator: self.previous_defs.append(current_line) @@ -714,7 +726,14 @@ def _maybe_empty_lines_for_class_or_def( else: newlines = 0 else: - newlines = 1 if current_line.depth else 2 + if ( + Preview.dummy_implementations in self.mode + and self.previous_line.is_stub_def + and (current_line.is_stub_def or current_line.is_decorator) + ): + newlines = user_hint_before + else: + newlines = 1 if current_line.depth else 2 if comment_to_add_newlines is not None: previous_block = comment_to_add_newlines.previous_block if previous_block is not None: From aaf9f5ea2d513bad96584ec0ea33891d9b8c7097 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 22 Jul 2023 12:25:11 -0700 Subject: [PATCH 3/9] remove final annotation --- src/black/lines.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index 460a9f66763..2f6b4138cf6 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -5,7 +5,6 @@ from typing import ( Callable, Dict, - Final, Iterator, List, Optional, @@ -587,7 +586,7 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: else: before = 0 - user_hint_before: Final = before + user_hint_before = before depth = current_line.depth previous_def = None From e5aaaa37b7a44d1bcb60482e76979183403af359 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 22 Jul 2023 13:42:43 -0700 Subject: [PATCH 4/9] test --- tests/data/miscellaneous/force_py36.py | 4 ++-- tests/data/preview/comments7.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/data/miscellaneous/force_py36.py b/tests/data/miscellaneous/force_py36.py index cad935e525a..4c9b70336e7 100644 --- a/tests/data/miscellaneous/force_py36.py +++ b/tests/data/miscellaneous/force_py36.py @@ -1,6 +1,6 @@ # The input source must not contain any Py36-specific syntax (e.g. argument type # annotations, trailing comma after *rest) or this test becomes invalid. -def long_function_name(argument_one, argument_two, argument_three, argument_four, argument_five, argument_six, *rest): ... +def long_function_name(argument_one, argument_two, argument_three, argument_four, argument_five, argument_six, *rest): pass # output # The input source must not contain any Py36-specific syntax (e.g. argument type # annotations, trailing comma after *rest) or this test becomes invalid. @@ -13,4 +13,4 @@ def long_function_name( argument_six, *rest, ): - ... + pass diff --git a/tests/data/preview/comments7.py b/tests/data/preview/comments7.py index ec2dc501d8e..8b1224017e5 100644 --- a/tests/data/preview/comments7.py +++ b/tests/data/preview/comments7.py @@ -278,8 +278,7 @@ class C: ) def test_fails_invalid_post_data( self, pyramid_config, db_request, post_data, message - ): - ... + ): ... square = Square(4) # type: Optional[Square] From 44378abc04bfd642d4b4ce877bd36aa01e119439 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 22 Jul 2023 13:49:28 -0700 Subject: [PATCH 5/9] test + changelog --- CHANGES.md | 2 + tests/data/preview/dummy_implementations.py | 54 +++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/data/preview/dummy_implementations.py diff --git a/CHANGES.md b/CHANGES.md index 709c767b329..d838f6c1da9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ +- More concise formatting for dummy implementations (#3796) + ### Configuration diff --git a/tests/data/preview/dummy_implementations.py b/tests/data/preview/dummy_implementations.py new file mode 100644 index 00000000000..1c711361946 --- /dev/null +++ b/tests/data/preview/dummy_implementations.py @@ -0,0 +1,54 @@ +from typing import NoReturn, Protocol, Union, overload + + +def dummy(a): ... +def other(b): ... + + +@overload +def a(arg: int) -> int: ... +@overload +def a(arg: str) -> str: ... +@overload +def a(arg: object) -> NoReturn: ... +def a(arg: Union[int, str, object]) -> Union[int, str]: + if not isinstance(arg, (int, str)): + raise TypeError + return arg + +class Proto(Protocol): + def foo(self, a: int) -> int: + ... + + def bar(self, b: str) -> str: ... + def baz(self, c: bytes) -> str: + ... + +# output + +from typing import NoReturn, Protocol, Union, overload + + +def dummy(a): ... +def other(b): ... + + +@overload +def a(arg: int) -> int: ... +@overload +def a(arg: str) -> str: ... +@overload +def a(arg: object) -> NoReturn: ... + + +def a(arg: Union[int, str, object]) -> Union[int, str]: + if not isinstance(arg, (int, str)): + raise TypeError + return arg + + +class Proto(Protocol): + def foo(self, a: int) -> int: ... + + def bar(self, b: str) -> str: ... + def baz(self, c: bytes) -> str: ... From 5b228b1c0bae2b5e8537f26f051bd793667f48f4 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 22 Jul 2023 13:55:33 -0700 Subject: [PATCH 6/9] more test case --- tests/data/preview/dummy_implementations.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/data/preview/dummy_implementations.py b/tests/data/preview/dummy_implementations.py index 1c711361946..1b9c6ba2649 100644 --- a/tests/data/preview/dummy_implementations.py +++ b/tests/data/preview/dummy_implementations.py @@ -24,6 +24,16 @@ def bar(self, b: str) -> str: ... def baz(self, c: bytes) -> str: ... + +def dummy_two(): + ... +@dummy +def dummy_three(): + ... + +def dummy_four(): + ... + # output from typing import NoReturn, Protocol, Union, overload @@ -52,3 +62,10 @@ def foo(self, a: int) -> int: ... def bar(self, b: str) -> str: ... def baz(self, c: bytes) -> str: ... + + +def dummy_two(): ... +@dummy +def dummy_three(): ... + +def dummy_four(): ... From 7de270e51cd55c4fcf05b74d5c5009e345af22e8 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 22 Jul 2023 19:19:39 -0700 Subject: [PATCH 7/9] change logic --- src/black/lines.py | 9 +++--- tests/data/preview/dummy_implementations.py | 32 +++++++++++++++++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index 2f6b4138cf6..0b46daf3ae8 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -725,14 +725,15 @@ def _maybe_empty_lines_for_class_or_def( # noqa: C901 else: newlines = 0 else: + newlines = 1 if current_line.depth else 2 + # If a user has left no space after a dummy implementation, don't insert + # new lines. This is useful for instance for @overload or Protocols. if ( Preview.dummy_implementations in self.mode and self.previous_line.is_stub_def - and (current_line.is_stub_def or current_line.is_decorator) + and not bool(user_hint_before) ): - newlines = user_hint_before - else: - newlines = 1 if current_line.depth else 2 + newlines = 0 if comment_to_add_newlines is not None: previous_block = comment_to_add_newlines.previous_block if previous_block is not None: diff --git a/tests/data/preview/dummy_implementations.py b/tests/data/preview/dummy_implementations.py index 1b9c6ba2649..e07c25ed129 100644 --- a/tests/data/preview/dummy_implementations.py +++ b/tests/data/preview/dummy_implementations.py @@ -34,6 +34,19 @@ def dummy_three(): def dummy_four(): ... +@overload +def b(arg: int) -> int: ... + +@overload +def b(arg: str) -> str: ... +@overload +def b(arg: object) -> NoReturn: ... + +def b(arg: Union[int, str, object]) -> Union[int, str]: + if not isinstance(arg, (int, str)): + raise TypeError + return arg + # output from typing import NoReturn, Protocol, Union, overload @@ -49,8 +62,6 @@ def a(arg: int) -> int: ... def a(arg: str) -> str: ... @overload def a(arg: object) -> NoReturn: ... - - def a(arg: Union[int, str, object]) -> Union[int, str]: if not isinstance(arg, (int, str)): raise TypeError @@ -68,4 +79,21 @@ def dummy_two(): ... @dummy def dummy_three(): ... + def dummy_four(): ... + + +@overload +def b(arg: int) -> int: ... + + +@overload +def b(arg: str) -> str: ... +@overload +def b(arg: object) -> NoReturn: ... + + +def b(arg: Union[int, str, object]) -> Union[int, str]: + if not isinstance(arg, (int, str)): + raise TypeError + return arg From b1c401495daf0c6f07bff0f7826ab45c841f5790 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 22 Jul 2023 19:21:50 -0700 Subject: [PATCH 8/9] include colon --- src/black/lines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/lines.py b/src/black/lines.py index 0b46daf3ae8..2de9fc83378 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -168,7 +168,7 @@ def is_def(self) -> bool: @property def is_stub_def(self) -> bool: """Is this line a function definition with a body consisting only of "..."?""" - return self.is_def and self.leaves[-3:] == [ + return self.is_def and self.leaves[-4:] == [Leaf(token.COLON, ":")] + [ Leaf(token.DOT, ".") for _ in range(3) ] From f1402526e7aed5ec90a38777c8887dbfc7407580 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 22 Jul 2023 19:26:17 -0700 Subject: [PATCH 9/9] better variable --- src/black/lines.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index 2de9fc83378..0a307b45eff 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -586,7 +586,7 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: else: before = 0 - user_hint_before = before + user_had_newline = bool(before) depth = current_line.depth previous_def = None @@ -598,7 +598,7 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: if self.mode.is_pyi: if depth and not current_line.is_def and self.previous_line.is_def: # Empty lines between attributes and methods should be preserved. - before = min(1, before) + before = 1 if user_had_newline else 0 elif ( Preview.blank_line_after_nested_stub_class in self.mode and previous_def.is_class @@ -634,7 +634,7 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: if current_line.is_decorator or current_line.is_def or current_line.is_class: return self._maybe_empty_lines_for_class_or_def( - current_line, before, user_hint_before + current_line, before, user_had_newline ) if ( @@ -660,7 +660,7 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: return before, 0 def _maybe_empty_lines_for_class_or_def( # noqa: C901 - self, current_line: Line, before: int, user_hint_before: int + self, current_line: Line, before: int, user_had_newline: bool ) -> Tuple[int, int]: if not current_line.is_decorator: self.previous_defs.append(current_line) @@ -731,7 +731,7 @@ def _maybe_empty_lines_for_class_or_def( # noqa: C901 if ( Preview.dummy_implementations in self.mode and self.previous_line.is_stub_def - and not bool(user_hint_before) + and not user_had_newline ): newlines = 0 if comment_to_add_newlines is not None: