Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix parentheses around return type annotations #13381

Merged
merged 4 commits into from
Sep 20, 2024

Conversation

MichaReiser
Copy link
Member

@MichaReiser MichaReiser commented Sep 17, 2024

Summary

This PR addresses the issue where subscript and other parenthesized expressions in function return type positions were wrapped in an extra pair of parentheses if the function has no parameters. This leads to rather verbose code:

 @pytest.fixture
-def mock_legacy_device_tracker_setup() -> (
-    Callable[[HomeAssistant, "MockScanner"], None]
-):
+def mock_legacy_device_tracker_setup() -> Callable[
+    [HomeAssistant, "MockScanner"], None
+]:
     """Return setup callable for legacy device tracker setup."""
     from tests.components.device_tracker.common import mock_legacy_device_tracker_setup

It's also inconsistent to how the same return type gets formatted when the function has parameters:

@pytest.fixture
def mock_legacy_device_tracker_setup(
    a,
) -> Callable[
    [HomeAssistant, "MoooooooooooooooooooooooooooooooooooooooooooockScanner"], None
]:
    """Return setup callable for legacy device tracker setup."""
    from tests.components.device_tracker.common import mock_legacy_device_tracker_setup

Fixes #9447

Black deviation

The PR adds two fairly extensive tests that walk through the black deviations. The most notable deviation is that black tries to parenthesize types before splitting the type itself (only applies to subscript)

Black

def func() -> (
    list[1, 2222222222222222222, 3333333333333333333333333333333333333333333333333]
):
    pass

But it only does so if the function has no parameters. Below is Black's formatting if the function has a parameter.

def func(
    a,
) -> list[
    1, 2222222222222222222, 33333333333333333333333333333333333333333333333333333
]:
    pass


def func(
    a,
) -> list[1, 2222222222222222222, 3333333333333333333333333333333333333333333333333333]:
    pass

This seems inconsistent. Ruff only parenthesizes the list if the subscript value up to the [ exceeds the line length

Ruff

def func() -> list[
    1, 2222222222222222222, 3333333333333333333333333333333333333333333333333
]:
    pass


def func(
    a,
) -> list[
    1, 2222222222222222222, 33333333333333333333333333333333333333333333333333333
]:
    pass


def func(
    a,
) -> list[1, 2222222222222222222, 3333333333333333333333333333333333333333333333333333]:
    pass


# Ruff uses parentheses here similar to Black
def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeedooooong() -> (
    list[int, float]
): ...

The main counterargument is that most changes in the ecosystem are cases where Black would parenthesize the subscript. I'm interested to get a second opinion on this.

Note: Supporting black's formatting would introduce a fair amount of extra complexity. We couldn't use maybe_parenthesize_expression anymore and need to use best_fitting to model the three cases (type fits, type parenthesized fits, split the type)

Preview

This is a preview-only change because it changes the formatting of already formatted code.

Test Plan

I reformatted some projects locally and skimmed through the changes. Overall, there are only few, and I generally prefer the new formatting (see this comment). Whether one prefers Black's formatting with the extra parentheses or not is probably the only "questionable" formatting change. IMO, consistency is more important (it reduces surprises for users: Why is it parenthesized here?).

@MichaReiser MichaReiser added formatter Related to the formatter preview Related to preview mode features labels Sep 17, 2024
Copy link
Contributor

github-actions bot commented Sep 17, 2024

ruff-ecosystem results

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

ℹ️ ecosystem check detected format changes. (+44 -58 lines in 7 files in 5 projects; 49 projects unchanged)

RasaHQ/rasa (+3 -3 lines across 1 file)

ruff format --preview

tests/core/test_tracker_stores.py~L309

     assert isinstance(tracker_store, InMemoryTrackerStore)
 
 
-async def _tracker_store_and_tracker_with_slot_set() -> (
-    Tuple[InMemoryTrackerStore, DialogueStateTracker]
-):
+async def _tracker_store_and_tracker_with_slot_set() -> Tuple[
+    InMemoryTrackerStore, DialogueStateTracker
+]:
     # returns an InMemoryTrackerStore containing a tracker with a slot set
 
     slot_key = "cuisine"

apache/superset (+3 -3 lines across 1 file)

ruff format --preview

tests/unit_tests/db_engine_specs/test_ocient.py~L223

     assert result == [expected]
 
 
-def _generate_gis_type_sanitization_test_cases() -> (
-    list[tuple[str, int, Any, dict[str, Any]]]
-):
+def _generate_gis_type_sanitization_test_cases() -> list[
+    tuple[str, int, Any, dict[str, Any]]
+]:
     if not ocient_is_installed():
         return []
 

langchain-ai/langchain (+8 -10 lines across 1 file)

ruff format --preview

libs/community/tests/integration_tests/retrievers/docarray/fixtures.py~L22

 
 
 @pytest.fixture
-def init_weaviate() -> (
-    Generator[
-        Tuple[WeaviateDocumentIndex, Dict[str, Any], FakeEmbeddings],
-        None,
-        None,
-    ]
-):
+def init_weaviate() -> Generator[
+    Tuple[WeaviateDocumentIndex, Dict[str, Any], FakeEmbeddings],
+    None,
+    None,
+]:
     """
     cd tests/integration_tests/vectorstores/docker-compose
     docker compose -f weaviate.yml up

libs/community/tests/integration_tests/retrievers/docarray/fixtures.py~L73

 
 
 @pytest.fixture
-def init_elastic() -> (
-    Generator[Tuple[ElasticDocIndex, Dict[str, Any], FakeEmbeddings], None, None]
-):
+def init_elastic() -> Generator[
+    Tuple[ElasticDocIndex, Dict[str, Any], FakeEmbeddings], None, None
+]:
     """
     cd tests/integration_tests/vectorstores/docker-compose
     docker-compose -f elasticsearch.yml up

rotki/rotki (+6 -6 lines across 1 file)

ruff format --preview

rotkehlchen/chain/evm/decoding/velodrome/velodrome_cache.py~L149

     return pool_addresses, gauge_addresses
 
 
-def read_velodrome_pools_and_gauges_from_cache() -> (
-    tuple[set[ChecksumEvmAddress], set[ChecksumEvmAddress]]
-):  # noqa: E501
+def read_velodrome_pools_and_gauges_from_cache() -> tuple[
+    set[ChecksumEvmAddress], set[ChecksumEvmAddress]
+]:  # noqa: E501
     """
     Reads globaldb cache and returns:
     - A set of all known addresses of velodrome pools.

rotkehlchen/chain/evm/decoding/velodrome/velodrome_cache.py~L163

     )
 
 
-def read_aerodrome_pools_and_gauges_from_cache() -> (
-    tuple[set[ChecksumEvmAddress], set[ChecksumEvmAddress]]
-):  # noqa: E501
+def read_aerodrome_pools_and_gauges_from_cache() -> tuple[
+    set[ChecksumEvmAddress], set[ChecksumEvmAddress]
+]:  # noqa: E501
     """
     Reads globaldb cache and returns:
     - A set of all known addresses of aerodrome pools.

python-trio/trio (+24 -36 lines across 3 files)

ruff format --preview

src/trio/_tests/test_ssl.py~L864

     client_ctx: SSLContext,
     https_compatible: bool,
 ) -> None:
-    async def stream_maker() -> (
-        tuple[
-            SSLStream[MemoryStapledStream],
-            SSLStream[MemoryStapledStream],
-        ]
-    ):
+    async def stream_maker() -> tuple[
+        SSLStream[MemoryStapledStream],
+        SSLStream[MemoryStapledStream],
+    ]:
         return ssl_memory_stream_pair(
             client_ctx,
             client_kwargs={"https_compatible": https_compatible},
             server_kwargs={"https_compatible": https_compatible},
         )
 
-    async def clogged_stream_maker() -> (
-        tuple[
-            SSLStream[MyStapledStream],
-            SSLStream[MyStapledStream],
-        ]
-    ):
+    async def clogged_stream_maker() -> tuple[
+        SSLStream[MyStapledStream],
+        SSLStream[MyStapledStream],
+    ]:
         client, server = ssl_lockstep_stream_pair(client_ctx)
         # If we don't do handshakes up front, then we run into a problem in
         # the following situation:

src/trio/_tests/test_testing.py~L604

 
     await check_one_way_stream(one_way_stream_maker, None)
 
-    async def half_closeable_stream_maker() -> (
-        tuple[
-            StapledStream[MemorySendStream, MemoryReceiveStream],
-            StapledStream[MemorySendStream, MemoryReceiveStream],
-        ]
-    ):
+    async def half_closeable_stream_maker() -> tuple[
+        StapledStream[MemorySendStream, MemoryReceiveStream],
+        StapledStream[MemorySendStream, MemoryReceiveStream],
+    ]:
         return memory_stream_pair()
 
     await check_half_closeable_stream(half_closeable_stream_maker, None)

src/trio/_tests/test_testing.py~L621

 
     await check_one_way_stream(one_way_stream_maker, one_way_stream_maker)
 
-    async def two_way_stream_maker() -> (
-        tuple[
-            StapledStream[SendStream, ReceiveStream],
-            StapledStream[SendStream, ReceiveStream],
-        ]
-    ):
+    async def two_way_stream_maker() -> tuple[
+        StapledStream[SendStream, ReceiveStream],
+        StapledStream[SendStream, ReceiveStream],
+    ]:
         return lockstep_stream_pair()
 
     await check_two_way_stream(two_way_stream_maker, two_way_stream_maker)

src/trio/testing/_memory_streams.py~L368

     return stream1, stream2
 
 
-def memory_stream_pair() -> (
-    tuple[
-        StapledStream[MemorySendStream, MemoryReceiveStream],
-        StapledStream[MemorySendStream, MemoryReceiveStream],
-    ]
-):
+def memory_stream_pair() -> tuple[
+    StapledStream[MemorySendStream, MemoryReceiveStream],
+    StapledStream[MemorySendStream, MemoryReceiveStream],
+]:
     """Create a connected, pure-Python, bidirectional stream with infinite
     buffering and flexible configuration options.
 

src/trio/testing/_memory_streams.py~L609

     return _LockstepSendStream(lbq), _LockstepReceiveStream(lbq)
 
 
-def lockstep_stream_pair() -> (
-    tuple[
-        StapledStream[SendStream, ReceiveStream],
-        StapledStream[SendStream, ReceiveStream],
-    ]
-):
+def lockstep_stream_pair() -> tuple[
+    StapledStream[SendStream, ReceiveStream],
+    StapledStream[SendStream, ReceiveStream],
+]:
     """Create a connected, pure-Python, bidirectional stream where data flows
     in lockstep.
 

@MichaReiser
Copy link
Member Author

MichaReiser commented Sep 17, 2024

Some more examples:

 @cache
-def _register_filesystems() -> (
-    Mapping[
-        str,
-        Callable[[str | None, Properties], AbstractFileSystem] | Callable[[str | None], AbstractFileSystem],
-    ]
-):
+def _register_filesystems() -> Mapping[
+    str,
+    Callable[[str | None, Properties], AbstractFileSystem] | Callable[[str | None], AbstractFileSystem],
+]:
     scheme_to_fs = _BUILTIN_SCHEME_TO_FS.copy()
     with Stats.timer("airflow.io.load_filesystems") as timer:


                 event_stream = exhaust_iterator_and_yield_results_with_exception(self)
 
-        def _threadpool_wrap_map_fn() -> (
-            Iterator[Union[Output, AssetMaterialization, AssetObservation, AssetCheckResult]]
-        ):
+        def _threadpool_wrap_map_fn() -> Iterator[
+            Union[Output, AssetMaterialization, AssetObservation, AssetCheckResult]
+        ]:
             with ThreadPoolExecutor(

                 if stt_audio_buffer:
                     # Send audio in the buffer first to speech-to-text, then move on to stt_stream.
                     # This is basically an async itertools.chain.
-                    async def buffer_then_audio_stream() -> (
-                        AsyncGenerator[ProcessedAudioChunk, None]
-                    ):
+                    async def buffer_then_audio_stream() -> AsyncGenerator[
+                        ProcessedAudioChunk, None
+                    ]:
                         # Buffered audio
                         for chunk in stt_audio_buffer:
                             yield chunk
diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py
index 12e5ed992c..2991bb4ef1 100644
--- a/homeassistant/components/openhome/media_player.py
+++ b/homeassistant/components/openhome/media_player.py
@@ -71,11 +71,9 @@ _ReturnFuncType = Callable[
 ]
 
 
-def catch_request_errors() -> (
-    Callable[
-        [_FuncType[_OpenhomeDeviceT, _P, _R]], _ReturnFuncType[_OpenhomeDeviceT, _P, _R]
-    ]
-):
+def catch_request_errors() -> Callable[
+    [_FuncType[_OpenhomeDeviceT, _P, _R]], _ReturnFuncType[_OpenhomeDeviceT, _P, _R]
+]:
     """Catch TimeoutError, aiohttp.ClientError, UpnpError errors."""
 
     def call_wrapper(
diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py
index 41cf4e22b5..19bdccbf83 100644
--- a/homeassistant/components/recorder/statistics.py
+++ b/homeassistant/components/recorder/statistics.py
@@ -935,12 +935,10 @@ def _reduce_statistics(
     return result
 
 
-def reduce_day_ts_factory() -> (
-    tuple[
-        Callable[[float, float], bool],
-        Callable[[float], tuple[float, float]],
-    ]
-):
+def reduce_day_ts_factory() -> tuple[
+    Callable[[float, float], bool],
+    Callable[[float], tuple[float, float]],
+]:
     """Return functions to match same day and day start end."""
     _boundries: tuple[float, float] = (0, 0)
 
@@ -983,12 +981,10 @@ def _reduce_statistics_per_day(
     )
 
 
-def reduce_week_ts_factory() -> (
-    tuple[
-        Callable[[float, float], bool],
-        Callable[[float], tuple[float, float]],
-    ]
-):
+def reduce_week_ts_factory() -> tuple[
+    Callable[[float, float], bool],
+    Callable[[float], tuple[float, float]],
+]:
     """Return functions to match same week and week start end."""
     _boundries: tuple[float, float] = (0, 0)
 
@@ -1041,12 +1037,10 @@ def _find_month_end_time(timestamp: datetime) -> datetime:
     )
 
 
-def reduce_month_ts_factory() -> (
-    tuple[
-        Callable[[float, float], bool],
-        Callable[[float], tuple[float, float]],
-    ]
-):
+def reduce_month_ts_factory() -> tuple[
+    Callable[[float, float], bool],
+    Callable[[float], tuple[float, float]],
+]:
     """Return functions to match same month and month start end."""
     _boundries: tuple[float, float] = (0, 0)
 
diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py
index 1eb964d82b..1bedc6bdac 100644
--- a/homeassistant/exceptions.py
+++ b/homeassistant/exceptions.py
@@ -15,9 +15,9 @@ if TYPE_CHECKING:
 _function_cache: dict[str, Callable[[str, str, dict[str, str] | None], str]] = {}
 
 
-def import_async_get_exception_message() -> (
-    Callable[[str, str, dict[str, str] | None], str]
-):
+def import_async_get_exception_message() -> Callable[
+    [str, str, dict[str, str] | None], str
+]:
     """Return a method that can fetch a translated exception message.
 
     Defaults to English, requires translations to already be cached.

diff --git a/tests/components/conftest.py b/tests/components/conftest.py
index bde8cad5ea..ce24f0d614 100644
--- a/tests/components/conftest.py
+++ b/tests/components/conftest.py
@@ -161,9 +161,9 @@ def mock_legacy_device_scanner() -> "MockScanner":
 
 
 @pytest.fixture
-def mock_legacy_device_tracker_setup() -> (
-    Callable[[HomeAssistant, "MockScanner"], None]
-):
+def mock_legacy_device_tracker_setup() -> Callable[
+    [HomeAssistant, "MockScanner"], None
+]:
     """Return setup callable for legacy device tracker setup."""
     from tests.components.device_tracker.common import mock_legacy_device_tracker_setup

@@ -82,30 +82,6 @@

func([(x, y,) for (x, y) in z], bar)

# Ensure that return type annotations (which use `parenthesize_if_expands`) are also hugged.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer true. We never add parentheses around lists anymore

@MichaReiser
Copy link
Member Author

MichaReiser commented Sep 17, 2024

Ruff preserves the parentheses around:

def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeedooooong() -> (
    list[int, float]
): ...

Edit: The reason why ruff adds parentheses above is because the ( is already past the configured line length limit. Adding parentheses in this case does seem reasonable because list would exceed the line length by more characters than adding ( and then breaking.

The argument against it is that we don't do so when the function has a parameter:

def a():
    def b():
        def c():
            def d():
                def e():
                    def f():
                        def g():
                            def h():
                                def i():
                                    def j():
                                        def k():
                                            def l():
                                                def m():
                                                    def n():
                                                        def o():
                                                            def p():
                                                                def q():
                                                                    def r():
                                                                        def s():
                                                                            def t():
                                                                                def u():
                                                                                    def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeedooooong(
                                                                                        a,
                                                                                    ) -> list[
                                                                                        int,
                                                                                        float,
                                                                                    ]: ...

But adding a ( would have a diminishing return in this case because it also indents the list by one extra level.

Overall. This seems rare enough and I think not parenthesizing is the better outcome but parenthesizing in the first helps stay in the configured line length (as much as possible)

@MichaReiser MichaReiser force-pushed the micha/avoid-parenthesizing-return-type-annotations branch from d32126c to 767ab25 Compare September 17, 2024 15:50
@MichaReiser MichaReiser force-pushed the micha/avoid-parenthesizing-return-type-annotations branch from 767ab25 to 12b9e22 Compare September 18, 2024 12:17
@MichaReiser MichaReiser force-pushed the micha/avoid-parenthesizing-return-type-annotations branch from 12b9e22 to 49e0aa3 Compare September 18, 2024 15:52
- and foo.bar.baz().bop(
- 1,
- )
+def double() -> first_item and foo.bar.baz().bop(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks surprising but it matches black's formatting

@MichaReiser MichaReiser marked this pull request as ready for review September 18, 2024 16:09
@MichaReiser

This comment was marked as resolved.

@MichaReiser MichaReiser force-pushed the micha/avoid-parenthesizing-return-type-annotations branch from a4c8940 to 7045b19 Compare September 18, 2024 16:45
@MichaReiser MichaReiser force-pushed the micha/avoid-parenthesizing-return-type-annotations branch from 7045b19 to 1b5fef1 Compare September 18, 2024 16:45
Copy link
Member

@charliermarsh charliermarsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting... I do prefer what you implemented here. I was looking back at #6436 -- I'm wondering why we didn't make this change then? I guess we were biasing towards improving compatibility as much as possible, despite us not understanding why Black had different styling at that point in time.

@MichaReiser
Copy link
Member Author

Interesting... I do prefer what you implemented here. I was looking back at #6436 -- I'm wondering why we didn't make this change then? I guess we were biasing towards improving compatibility as much as possible, despite us not understanding why Black had different styling at that point in time.

Looking through the tests added in #6436 the once that are relevant for this change are

-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
-    Set[
-        "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-    ]
-): ...
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]: ...
 
 
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
-    Set[
-        "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-    ]
-): ...
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]: ...
 
 
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
-    Set[
-        "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-    ]
-): ...
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]: ...
 
 
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
-    Set[
-        "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-    ]
-): ...
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]: ...

These are all cases where black keeps the parentheses. But it removes them as soon as the subscript contains another element

def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
]: ...

That's why I see this mainly as a refinement of the formatting you implemented in #6436.

@MichaReiser MichaReiser merged commit 531ebf6 into main Sep 20, 2024
20 checks passed
@MichaReiser MichaReiser deleted the micha/avoid-parenthesizing-return-type-annotations branch September 20, 2024 07:23
@MichaReiser MichaReiser mentioned this pull request Sep 24, 2024
25 tasks
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Oct 7, 2024
## 0.6.9

### Preview features

- Fix codeblock dynamic line length calculation for indented docstring examples ([#13523](astral-sh/ruff#13523))
- \[`refurb`\] Mark `FURB118` fix as unsafe ([#13613](astral-sh/ruff#13613))

### Rule changes

- \[`pydocstyle`\] Don't raise `D208` when last line is non-empty ([#13372](astral-sh/ruff#13372))
- \[`pylint`\] Preserve trivia (i.e. comments) in `PLR5501` autofix ([#13573](astral-sh/ruff#13573))

### Configuration

- \[`pyflakes`\] Add `allow-unused-imports` setting for `unused-import` rule (`F401`) ([#13601](astral-sh/ruff#13601))

### Bug fixes

- Support ruff discovery in pip build environments ([#13591](astral-sh/ruff#13591))
- \[`flake8-bugbear`\] Avoid short circuiting `B017` for multiple context managers ([#13609](astral-sh/ruff#13609))
- \[`pylint`\] Do not offer an invalid fix for `PLR1716` when the comparisons contain parenthesis ([#13527](astral-sh/ruff#13527))
- \[`pyupgrade`\] Fix `UP043` to apply to `collections.abc.Generator` and `collections.abc.AsyncGenerator` ([#13611](astral-sh/ruff#13611))
- \[`refurb`\] Fix handling of slices in tuples for `FURB118`, e.g., `x[:, 1]` ([#13518](astral-sh/ruff#13518))

### Documentation

- Update GitHub Action link to `astral-sh/ruff-action` ([#13551](astral-sh/ruff#13551))

## 0.6.8

### Preview features

- Remove unnecessary parentheses around `match case` clauses ([#13510](astral-sh/ruff#13510))
- Parenthesize overlong `if` guards in `match..case` clauses ([#13513](astral-sh/ruff#13513))
- Detect basic wildcard imports in `ruff analyze graph` ([#13486](astral-sh/ruff#13486))
- \[`pylint`\] Implement `boolean-chained-comparison` (`R1716`) ([#13435](astral-sh/ruff#13435))

### Rule changes

- \[`lake8-simplify`\] Detect `SIM910` when using variadic keyword arguments, i.e., `**kwargs` ([#13503](astral-sh/ruff#13503))
- \[`pyupgrade`\] Avoid false negatives with non-reference shadowed bindings of loop variables (`UP028`) ([#13504](astral-sh/ruff#13504))

### Bug fixes

- Detect tuples bound to variadic positional arguments i.e. `*args` ([#13512](astral-sh/ruff#13512))
- Exit gracefully on broken pipe errors ([#13485](astral-sh/ruff#13485))
- Avoid panic when analyze graph hits broken pipe ([#13484](astral-sh/ruff#13484))

### Performance

- Reuse `BTreeSets` in module resolver ([#13440](astral-sh/ruff#13440))
- Skip traversal for non-compound statements ([#13441](astral-sh/ruff#13441))

## 0.6.7

### Preview features

- Add Python version support to ruff analyze CLI ([#13426](astral-sh/ruff#13426))
- Add `exclude` support to `ruff analyze` ([#13425](astral-sh/ruff#13425))
- Fix parentheses around return type annotations ([#13381](astral-sh/ruff#13381))

### Rule changes

- \[`pycodestyle`\] Fix: Don't autofix if the first line ends in a question mark? (D400) ([#13399](astral-sh/ruff#13399))

### Bug fixes

- Respect `lint.exclude` in ruff check `--add-noqa` ([#13427](astral-sh/ruff#13427))

### Performance

- Avoid tracking module resolver files in Salsa ([#13437](astral-sh/ruff#13437))
- Use `forget` for module resolver database ([#13438](astral-sh/ruff#13438))

## 0.6.6

### Preview features

- \[`refurb`\] Skip `slice-to-remove-prefix-or-suffix` (`FURB188`) when non-trivial slice steps are present ([#13405](astral-sh/ruff#13405))
- Add a subcommand to generate dependency graphs ([#13402](astral-sh/ruff#13402))

### Formatter

- Fix placement of inline parameter comments ([#13379](astral-sh/ruff#13379))

### Server

- Fix off-by one error in the `LineIndex::offset` calculation ([#13407](astral-sh/ruff#13407))

### Bug fixes

- \[`fastapi`\] Respect FastAPI aliases in route definitions ([#13394](astral-sh/ruff#13394))
- \[`pydocstyle`\] Respect word boundaries when detecting function signature in docs ([#13388](astral-sh/ruff#13388))

### Documentation

- Add backlinks to rule overview linter ([#13368](astral-sh/ruff#13368))
- Fix documentation for editor vim plugin ALE ([#13348](astral-sh/ruff#13348))
- Fix rendering of `FURB188` docs ([#13406](astral-sh/ruff#13406))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
formatter Related to the formatter preview Related to preview mode features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Formatter: Unnecessary parentheses around return type annotations
2 participants