[ruff] Extend FA102 with listed PEP 585-compatible APIs (#20659)

Resolves https://github.com/astral-sh/ruff/issues/20512

This PR expands FA102’s preview coverage to flag every
PEP 585-compatible API that breaks without from `from __future__ import
annotations`, including `collections.abc`. The rule now treats asyncio
futures, pathlib-style queues, weakref containers, shelve proxies, and
the full `collections.abc` family as generics once preview mode is
enabled.

Stable behavior is unchanged; the broader matching runs behind
`is_future_required_preview_generics_enabled`, letting us vet the new
diagnostics before marking them as stable.

I've also added a snapshot test that covers all of the newly supported
types.

Check out
https://docs.python.org/3/library/stdtypes.html#standard-generic-classes
for a list of commonly used PEP 585-compatible APIs.
This commit is contained in:
liam 2025-10-03 09:45:32 -04:00 committed by GitHub
parent 7d7237c660
commit ebfb33c30b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1049 additions and 27 deletions

View file

@ -0,0 +1,91 @@
import asyncio
import collections
import contextlib
import dataclasses
import functools
import os
import queue
import re
import shelve
import types
import weakref
from collections.abc import (
AsyncGenerator,
AsyncIterable,
AsyncIterator,
Awaitable,
ByteString,
Callable,
Collection,
Container,
Coroutine,
Generator,
Iterable,
Iterator,
ItemsView,
KeysView,
Mapping,
MappingView,
MutableMapping,
MutableSequence,
MutableSet,
Reversible,
Sequence,
Set,
ValuesView,
)
def takes_preview_generics(
future: asyncio.Future[int],
task: asyncio.Task[str],
deque_object: collections.deque[int],
defaultdict_object: collections.defaultdict[str, int],
ordered_dict: collections.OrderedDict[str, int],
counter_obj: collections.Counter[str],
chain_map: collections.ChainMap[str, int],
context_manager: contextlib.AbstractContextManager[str],
async_context_manager: contextlib.AbstractAsyncContextManager[int],
dataclass_field: dataclasses.Field[int],
cached_prop: functools.cached_property[int],
partial_method: functools.partialmethod[int],
path_like: os.PathLike[str],
lifo_queue: queue.LifoQueue[int],
regular_queue: queue.Queue[int],
priority_queue: queue.PriorityQueue[int],
simple_queue: queue.SimpleQueue[int],
regex_pattern: re.Pattern[str],
regex_match: re.Match[str],
bsd_db_shelf: shelve.BsdDbShelf[str, int],
db_filename_shelf: shelve.DbfilenameShelf[str, int],
shelf_obj: shelve.Shelf[str, int],
mapping_proxy: types.MappingProxyType[str, int],
weak_key_dict: weakref.WeakKeyDictionary[object, int],
weak_method: weakref.WeakMethod[int],
weak_set: weakref.WeakSet[int],
weak_value_dict: weakref.WeakValueDictionary[object, int],
awaitable: Awaitable[int],
coroutine: Coroutine[int, None, str],
async_iterable: AsyncIterable[int],
async_iterator: AsyncIterator[int],
async_generator: AsyncGenerator[int, None],
iterable: Iterable[int],
iterator: Iterator[int],
generator: Generator[int, None, None],
reversible: Reversible[int],
container: Container[int],
collection: Collection[int],
callable_obj: Callable[[int], str],
set_obj: Set[int],
mutable_set: MutableSet[int],
mapping: Mapping[str, int],
mutable_mapping: MutableMapping[str, int],
sequence: Sequence[int],
mutable_sequence: MutableSequence[int],
byte_string: ByteString[int],
mapping_view: MappingView[str, int],
keys_view: KeysView[str],
items_view: ItemsView[str, int],
values_view: ValuesView[int],
) -> None:
...

View file

@ -8,7 +8,8 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::preview::{ use crate::preview::{
is_optional_as_none_in_union_enabled, is_unnecessary_default_type_args_stubs_enabled, is_future_required_preview_generics_enabled, is_optional_as_none_in_union_enabled,
is_unnecessary_default_type_args_stubs_enabled,
}; };
use crate::registry::Rule; use crate::registry::Rule;
use crate::rules::{ use crate::rules::{
@ -69,7 +70,11 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
&& checker.semantic.in_annotation() && checker.semantic.in_annotation()
&& checker.semantic.in_runtime_evaluated_annotation() && checker.semantic.in_runtime_evaluated_annotation()
&& !checker.semantic.in_string_type_definition() && !checker.semantic.in_string_type_definition()
&& typing::is_pep585_generic(value, &checker.semantic) && typing::is_pep585_generic(
value,
&checker.semantic,
is_future_required_preview_generics_enabled(checker.settings()),
)
{ {
flake8_future_annotations::rules::future_required_type_annotation( flake8_future_annotations::rules::future_required_type_annotation(
checker, checker,

View file

@ -200,6 +200,11 @@ pub(crate) const fn is_optional_as_none_in_union_enabled(settings: &LinterSettin
settings.preview.is_enabled() settings.preview.is_enabled()
} }
// https://github.com/astral-sh/ruff/pull/20659
pub(crate) const fn is_future_required_preview_generics_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/18683 // https://github.com/astral-sh/ruff/pull/18683
pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled( pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
settings: &LinterSettings, settings: &LinterSettings,

View file

@ -9,6 +9,7 @@ mod tests {
use test_case::test_case; use test_case::test_case;
use crate::registry::Rule; use crate::registry::Rule;
use crate::settings::types::PreviewMode;
use crate::test::test_path; use crate::test::test_path;
use crate::{assert_diagnostics, settings}; use crate::{assert_diagnostics, settings};
use ruff_python_ast::PythonVersion; use ruff_python_ast::PythonVersion;
@ -39,6 +40,7 @@ mod tests {
} }
#[test_case(Path::new("no_future_import_uses_lowercase.py"))] #[test_case(Path::new("no_future_import_uses_lowercase.py"))]
#[test_case(Path::new("no_future_import_uses_preview_generics.py"))]
#[test_case(Path::new("no_future_import_uses_union.py"))] #[test_case(Path::new("no_future_import_uses_union.py"))]
#[test_case(Path::new("no_future_import_uses_union_inner.py"))] #[test_case(Path::new("no_future_import_uses_union_inner.py"))]
#[test_case(Path::new("ok_no_types.py"))] #[test_case(Path::new("ok_no_types.py"))]
@ -56,4 +58,19 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics); assert_diagnostics!(snapshot, diagnostics);
Ok(()) Ok(())
} }
#[test_case(Path::new("no_future_import_uses_preview_generics.py"))]
fn fa102_preview(path: &Path) -> Result<()> {
let snapshot = format!("fa102_preview_{}", path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_future_annotations").join(path).as_path(),
&settings::LinterSettings {
unresolved_target_version: PythonVersion::PY37.into(),
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(Rule::FutureRequiredTypeAnnotation)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
} }

View file

@ -0,0 +1,36 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
---
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:42:19
|
40 | future: asyncio.Future[int],
41 | task: asyncio.Task[str],
42 | deque_object: collections.deque[int],
| ^^^^^^^^^^^^^^^^^^^^^^
43 | defaultdict_object: collections.defaultdict[str, int],
44 | ordered_dict: collections.OrderedDict[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:43:25
|
41 | task: asyncio.Task[str],
42 | deque_object: collections.deque[int],
43 | defaultdict_object: collections.defaultdict[str, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44 | ordered_dict: collections.OrderedDict[str, int],
45 | counter_obj: collections.Counter[str],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior

View file

@ -0,0 +1,851 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
---
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:40:13
|
39 | def takes_preview_generics(
40 | future: asyncio.Future[int],
| ^^^^^^^^^^^^^^^^^^^
41 | task: asyncio.Task[str],
42 | deque_object: collections.deque[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:41:11
|
39 | def takes_preview_generics(
40 | future: asyncio.Future[int],
41 | task: asyncio.Task[str],
| ^^^^^^^^^^^^^^^^^
42 | deque_object: collections.deque[int],
43 | defaultdict_object: collections.defaultdict[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:42:19
|
40 | future: asyncio.Future[int],
41 | task: asyncio.Task[str],
42 | deque_object: collections.deque[int],
| ^^^^^^^^^^^^^^^^^^^^^^
43 | defaultdict_object: collections.defaultdict[str, int],
44 | ordered_dict: collections.OrderedDict[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:43:25
|
41 | task: asyncio.Task[str],
42 | deque_object: collections.deque[int],
43 | defaultdict_object: collections.defaultdict[str, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44 | ordered_dict: collections.OrderedDict[str, int],
45 | counter_obj: collections.Counter[str],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:44:19
|
42 | deque_object: collections.deque[int],
43 | defaultdict_object: collections.defaultdict[str, int],
44 | ordered_dict: collections.OrderedDict[str, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45 | counter_obj: collections.Counter[str],
46 | chain_map: collections.ChainMap[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:45:18
|
43 | defaultdict_object: collections.defaultdict[str, int],
44 | ordered_dict: collections.OrderedDict[str, int],
45 | counter_obj: collections.Counter[str],
| ^^^^^^^^^^^^^^^^^^^^^^^^
46 | chain_map: collections.ChainMap[str, int],
47 | context_manager: contextlib.AbstractContextManager[str],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:46:16
|
44 | ordered_dict: collections.OrderedDict[str, int],
45 | counter_obj: collections.Counter[str],
46 | chain_map: collections.ChainMap[str, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
47 | context_manager: contextlib.AbstractContextManager[str],
48 | async_context_manager: contextlib.AbstractAsyncContextManager[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:47:22
|
45 | counter_obj: collections.Counter[str],
46 | chain_map: collections.ChainMap[str, int],
47 | context_manager: contextlib.AbstractContextManager[str],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
48 | async_context_manager: contextlib.AbstractAsyncContextManager[int],
49 | dataclass_field: dataclasses.Field[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:48:28
|
46 | chain_map: collections.ChainMap[str, int],
47 | context_manager: contextlib.AbstractContextManager[str],
48 | async_context_manager: contextlib.AbstractAsyncContextManager[int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49 | dataclass_field: dataclasses.Field[int],
50 | cached_prop: functools.cached_property[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:49:22
|
47 | context_manager: contextlib.AbstractContextManager[str],
48 | async_context_manager: contextlib.AbstractAsyncContextManager[int],
49 | dataclass_field: dataclasses.Field[int],
| ^^^^^^^^^^^^^^^^^^^^^^
50 | cached_prop: functools.cached_property[int],
51 | partial_method: functools.partialmethod[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:50:18
|
48 | async_context_manager: contextlib.AbstractAsyncContextManager[int],
49 | dataclass_field: dataclasses.Field[int],
50 | cached_prop: functools.cached_property[int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
51 | partial_method: functools.partialmethod[int],
52 | path_like: os.PathLike[str],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:51:21
|
49 | dataclass_field: dataclasses.Field[int],
50 | cached_prop: functools.cached_property[int],
51 | partial_method: functools.partialmethod[int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 | path_like: os.PathLike[str],
53 | lifo_queue: queue.LifoQueue[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:52:16
|
50 | cached_prop: functools.cached_property[int],
51 | partial_method: functools.partialmethod[int],
52 | path_like: os.PathLike[str],
| ^^^^^^^^^^^^^^^^
53 | lifo_queue: queue.LifoQueue[int],
54 | regular_queue: queue.Queue[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:53:17
|
51 | partial_method: functools.partialmethod[int],
52 | path_like: os.PathLike[str],
53 | lifo_queue: queue.LifoQueue[int],
| ^^^^^^^^^^^^^^^^^^^^
54 | regular_queue: queue.Queue[int],
55 | priority_queue: queue.PriorityQueue[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:54:20
|
52 | path_like: os.PathLike[str],
53 | lifo_queue: queue.LifoQueue[int],
54 | regular_queue: queue.Queue[int],
| ^^^^^^^^^^^^^^^^
55 | priority_queue: queue.PriorityQueue[int],
56 | simple_queue: queue.SimpleQueue[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:55:21
|
53 | lifo_queue: queue.LifoQueue[int],
54 | regular_queue: queue.Queue[int],
55 | priority_queue: queue.PriorityQueue[int],
| ^^^^^^^^^^^^^^^^^^^^^^^^
56 | simple_queue: queue.SimpleQueue[int],
57 | regex_pattern: re.Pattern[str],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:56:19
|
54 | regular_queue: queue.Queue[int],
55 | priority_queue: queue.PriorityQueue[int],
56 | simple_queue: queue.SimpleQueue[int],
| ^^^^^^^^^^^^^^^^^^^^^^
57 | regex_pattern: re.Pattern[str],
58 | regex_match: re.Match[str],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:57:20
|
55 | priority_queue: queue.PriorityQueue[int],
56 | simple_queue: queue.SimpleQueue[int],
57 | regex_pattern: re.Pattern[str],
| ^^^^^^^^^^^^^^^
58 | regex_match: re.Match[str],
59 | bsd_db_shelf: shelve.BsdDbShelf[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:58:18
|
56 | simple_queue: queue.SimpleQueue[int],
57 | regex_pattern: re.Pattern[str],
58 | regex_match: re.Match[str],
| ^^^^^^^^^^^^^
59 | bsd_db_shelf: shelve.BsdDbShelf[str, int],
60 | db_filename_shelf: shelve.DbfilenameShelf[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:59:19
|
57 | regex_pattern: re.Pattern[str],
58 | regex_match: re.Match[str],
59 | bsd_db_shelf: shelve.BsdDbShelf[str, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
60 | db_filename_shelf: shelve.DbfilenameShelf[str, int],
61 | shelf_obj: shelve.Shelf[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:60:24
|
58 | regex_match: re.Match[str],
59 | bsd_db_shelf: shelve.BsdDbShelf[str, int],
60 | db_filename_shelf: shelve.DbfilenameShelf[str, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
61 | shelf_obj: shelve.Shelf[str, int],
62 | mapping_proxy: types.MappingProxyType[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:61:16
|
59 | bsd_db_shelf: shelve.BsdDbShelf[str, int],
60 | db_filename_shelf: shelve.DbfilenameShelf[str, int],
61 | shelf_obj: shelve.Shelf[str, int],
| ^^^^^^^^^^^^^^^^^^^^^^
62 | mapping_proxy: types.MappingProxyType[str, int],
63 | weak_key_dict: weakref.WeakKeyDictionary[object, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:62:20
|
60 | db_filename_shelf: shelve.DbfilenameShelf[str, int],
61 | shelf_obj: shelve.Shelf[str, int],
62 | mapping_proxy: types.MappingProxyType[str, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
63 | weak_key_dict: weakref.WeakKeyDictionary[object, int],
64 | weak_method: weakref.WeakMethod[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:63:20
|
61 | shelf_obj: shelve.Shelf[str, int],
62 | mapping_proxy: types.MappingProxyType[str, int],
63 | weak_key_dict: weakref.WeakKeyDictionary[object, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
64 | weak_method: weakref.WeakMethod[int],
65 | weak_set: weakref.WeakSet[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:64:18
|
62 | mapping_proxy: types.MappingProxyType[str, int],
63 | weak_key_dict: weakref.WeakKeyDictionary[object, int],
64 | weak_method: weakref.WeakMethod[int],
| ^^^^^^^^^^^^^^^^^^^^^^^
65 | weak_set: weakref.WeakSet[int],
66 | weak_value_dict: weakref.WeakValueDictionary[object, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:65:15
|
63 | weak_key_dict: weakref.WeakKeyDictionary[object, int],
64 | weak_method: weakref.WeakMethod[int],
65 | weak_set: weakref.WeakSet[int],
| ^^^^^^^^^^^^^^^^^^^^
66 | weak_value_dict: weakref.WeakValueDictionary[object, int],
67 | awaitable: Awaitable[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:66:22
|
64 | weak_method: weakref.WeakMethod[int],
65 | weak_set: weakref.WeakSet[int],
66 | weak_value_dict: weakref.WeakValueDictionary[object, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
67 | awaitable: Awaitable[int],
68 | coroutine: Coroutine[int, None, str],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:67:16
|
65 | weak_set: weakref.WeakSet[int],
66 | weak_value_dict: weakref.WeakValueDictionary[object, int],
67 | awaitable: Awaitable[int],
| ^^^^^^^^^^^^^^
68 | coroutine: Coroutine[int, None, str],
69 | async_iterable: AsyncIterable[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:68:16
|
66 | weak_value_dict: weakref.WeakValueDictionary[object, int],
67 | awaitable: Awaitable[int],
68 | coroutine: Coroutine[int, None, str],
| ^^^^^^^^^^^^^^^^^^^^^^^^^
69 | async_iterable: AsyncIterable[int],
70 | async_iterator: AsyncIterator[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:69:21
|
67 | awaitable: Awaitable[int],
68 | coroutine: Coroutine[int, None, str],
69 | async_iterable: AsyncIterable[int],
| ^^^^^^^^^^^^^^^^^^
70 | async_iterator: AsyncIterator[int],
71 | async_generator: AsyncGenerator[int, None],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:70:21
|
68 | coroutine: Coroutine[int, None, str],
69 | async_iterable: AsyncIterable[int],
70 | async_iterator: AsyncIterator[int],
| ^^^^^^^^^^^^^^^^^^
71 | async_generator: AsyncGenerator[int, None],
72 | iterable: Iterable[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:71:22
|
69 | async_iterable: AsyncIterable[int],
70 | async_iterator: AsyncIterator[int],
71 | async_generator: AsyncGenerator[int, None],
| ^^^^^^^^^^^^^^^^^^^^^^^^^
72 | iterable: Iterable[int],
73 | iterator: Iterator[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:72:15
|
70 | async_iterator: AsyncIterator[int],
71 | async_generator: AsyncGenerator[int, None],
72 | iterable: Iterable[int],
| ^^^^^^^^^^^^^
73 | iterator: Iterator[int],
74 | generator: Generator[int, None, None],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:73:15
|
71 | async_generator: AsyncGenerator[int, None],
72 | iterable: Iterable[int],
73 | iterator: Iterator[int],
| ^^^^^^^^^^^^^
74 | generator: Generator[int, None, None],
75 | reversible: Reversible[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:74:16
|
72 | iterable: Iterable[int],
73 | iterator: Iterator[int],
74 | generator: Generator[int, None, None],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
75 | reversible: Reversible[int],
76 | container: Container[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:75:17
|
73 | iterator: Iterator[int],
74 | generator: Generator[int, None, None],
75 | reversible: Reversible[int],
| ^^^^^^^^^^^^^^^
76 | container: Container[int],
77 | collection: Collection[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:76:16
|
74 | generator: Generator[int, None, None],
75 | reversible: Reversible[int],
76 | container: Container[int],
| ^^^^^^^^^^^^^^
77 | collection: Collection[int],
78 | callable_obj: Callable[[int], str],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:77:17
|
75 | reversible: Reversible[int],
76 | container: Container[int],
77 | collection: Collection[int],
| ^^^^^^^^^^^^^^^
78 | callable_obj: Callable[[int], str],
79 | set_obj: Set[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:78:19
|
76 | container: Container[int],
77 | collection: Collection[int],
78 | callable_obj: Callable[[int], str],
| ^^^^^^^^^^^^^^^^^^^^
79 | set_obj: Set[int],
80 | mutable_set: MutableSet[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:79:14
|
77 | collection: Collection[int],
78 | callable_obj: Callable[[int], str],
79 | set_obj: Set[int],
| ^^^^^^^^
80 | mutable_set: MutableSet[int],
81 | mapping: Mapping[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:80:18
|
78 | callable_obj: Callable[[int], str],
79 | set_obj: Set[int],
80 | mutable_set: MutableSet[int],
| ^^^^^^^^^^^^^^^
81 | mapping: Mapping[str, int],
82 | mutable_mapping: MutableMapping[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:81:14
|
79 | set_obj: Set[int],
80 | mutable_set: MutableSet[int],
81 | mapping: Mapping[str, int],
| ^^^^^^^^^^^^^^^^^
82 | mutable_mapping: MutableMapping[str, int],
83 | sequence: Sequence[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:82:22
|
80 | mutable_set: MutableSet[int],
81 | mapping: Mapping[str, int],
82 | mutable_mapping: MutableMapping[str, int],
| ^^^^^^^^^^^^^^^^^^^^^^^^
83 | sequence: Sequence[int],
84 | mutable_sequence: MutableSequence[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:83:15
|
81 | mapping: Mapping[str, int],
82 | mutable_mapping: MutableMapping[str, int],
83 | sequence: Sequence[int],
| ^^^^^^^^^^^^^
84 | mutable_sequence: MutableSequence[int],
85 | byte_string: ByteString[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:84:23
|
82 | mutable_mapping: MutableMapping[str, int],
83 | sequence: Sequence[int],
84 | mutable_sequence: MutableSequence[int],
| ^^^^^^^^^^^^^^^^^^^^
85 | byte_string: ByteString[int],
86 | mapping_view: MappingView[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:85:18
|
83 | sequence: Sequence[int],
84 | mutable_sequence: MutableSequence[int],
85 | byte_string: ByteString[int],
| ^^^^^^^^^^^^^^^
86 | mapping_view: MappingView[str, int],
87 | keys_view: KeysView[str],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:86:19
|
84 | mutable_sequence: MutableSequence[int],
85 | byte_string: ByteString[int],
86 | mapping_view: MappingView[str, int],
| ^^^^^^^^^^^^^^^^^^^^^
87 | keys_view: KeysView[str],
88 | items_view: ItemsView[str, int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:87:16
|
85 | byte_string: ByteString[int],
86 | mapping_view: MappingView[str, int],
87 | keys_view: KeysView[str],
| ^^^^^^^^^^^^^
88 | items_view: ItemsView[str, int],
89 | values_view: ValuesView[int],
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:88:17
|
86 | mapping_view: MappingView[str, int],
87 | keys_view: KeysView[str],
88 | items_view: ItemsView[str, int],
| ^^^^^^^^^^^^^^^^^^^
89 | values_view: ValuesView[int],
90 | ) -> None:
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior
FA102 [*] Missing `from __future__ import annotations`, but uses PEP 585 collection
--> no_future_import_uses_preview_generics.py:89:18
|
87 | keys_view: KeysView[str],
88 | items_view: ItemsView[str, int],
89 | values_view: ValuesView[int],
| ^^^^^^^^^^^^^^^
90 | ) -> None:
91 | ...
|
help: Add `from __future__ import annotations`
1 + from __future__ import annotations
2 | import asyncio
3 | import collections
4 | import contextlib
note: This is an unsafe fix and may change runtime behavior

View file

@ -8,11 +8,10 @@ use ruff_python_ast::{
StmtAssign, StmtAssign,
}; };
use ruff_python_stdlib::typing::{ use ruff_python_stdlib::typing::{
as_pep_585_generic, has_pep_585_generic, is_immutable_generic_type, as_pep_585_generic, is_immutable_generic_type, is_immutable_non_generic_type,
is_immutable_non_generic_type, is_immutable_return_type, is_literal_member, is_immutable_return_type, is_literal_member, is_mutable_return_type, is_pep_593_generic_member,
is_mutable_return_type, is_pep_593_generic_member, is_pep_593_generic_type, is_pep_593_generic_type, is_standard_library_generic, is_standard_library_generic_member,
is_standard_library_generic, is_standard_library_generic_member, is_standard_library_literal, is_standard_library_literal, is_typed_dict, is_typed_dict_member,
is_typed_dict, is_typed_dict_member,
}; };
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
@ -148,14 +147,46 @@ pub fn to_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> Option<Module
} }
/// Return whether a given expression uses a PEP 585 standard library generic. /// Return whether a given expression uses a PEP 585 standard library generic.
pub fn is_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> bool { pub fn is_pep585_generic(
expr: &Expr,
semantic: &SemanticModel,
include_preview_generics: bool,
) -> bool {
semantic semantic
.resolve_qualified_name(expr) .resolve_qualified_name(expr)
.is_some_and(|qualified_name| { .is_some_and(|qualified_name| match qualified_name.segments() {
let [module, name] = qualified_name.segments() else { ["", "dict" | "frozenset" | "list" | "set" | "tuple" | "type"]
return false; | ["collections", "deque" | "defaultdict"] => true,
}; ["asyncio", "Future" | "Task"]
has_pep_585_generic(module, name) | ["collections", "ChainMap" | "Counter" | "OrderedDict"]
| [
"contextlib",
"AbstractAsyncContextManager" | "AbstractContextManager",
]
| ["dataclasses", "Field"]
| ["functools", "cached_property" | "partialmethod"]
| ["os", "PathLike"]
| [
"queue",
"LifoQueue" | "PriorityQueue" | "Queue" | "SimpleQueue",
]
| ["re", "Match" | "Pattern"]
| ["shelve", "BsdDbShelf" | "DbfilenameShelf" | "Shelf"]
| ["types", "MappingProxyType"]
| [
"weakref",
"WeakKeyDictionary" | "WeakMethod" | "WeakSet" | "WeakValueDictionary",
]
| [
"collections",
"abc",
"AsyncGenerator" | "AsyncIterable" | "AsyncIterator" | "Awaitable" | "ByteString"
| "Callable" | "Collection" | "Container" | "Coroutine" | "Generator" | "ItemsView"
| "Iterable" | "Iterator" | "KeysView" | "Mapping" | "MappingView"
| "MutableMapping" | "MutableSequence" | "MutableSet" | "Reversible" | "Sequence"
| "Set" | "ValuesView",
] => include_preview_generics,
_ => false,
}) })
} }

View file

@ -354,20 +354,6 @@ pub fn as_pep_585_generic(module: &str, member: &str) -> Option<ModuleMember> {
} }
} }
/// Given a typing member, returns `true` if a generic equivalent exists in the Python standard
/// library (e.g., `list` for `typing.List`), as introduced by [PEP 585].
///
/// [PEP 585]: https://peps.python.org/pep-0585/
pub fn has_pep_585_generic(module: &str, member: &str) -> bool {
// Constructed by taking every pattern from `as_pep_585_generic`, removing all but
// the last element in each pattern, and de-duplicating the values.
matches!(
(module, member),
("", "dict" | "frozenset" | "list" | "set" | "tuple" | "type")
| ("collections", "deque" | "defaultdict")
)
}
/// Returns the expected return type for a magic method. /// Returns the expected return type for a magic method.
/// ///
/// See: <https://github.com/JelleZijlstra/autotyping/blob/0adba5ba0eee33c1de4ad9d0c79acfd737321dd9/autotyping/autotyping.py#L69-L91> /// See: <https://github.com/JelleZijlstra/autotyping/blob/0adba5ba0eee33c1de4ad9d0c79acfd737321dd9/autotyping/autotyping.py#L69-L91>