diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_preview_generics.py b/crates/ruff_linter/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_preview_generics.py new file mode 100644 index 0000000000..23a35a077e --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_preview_generics.py @@ -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: + ... diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index d1a013f0af..4e9d6eba96 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -8,7 +8,8 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; 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::rules::{ @@ -69,7 +70,11 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { && checker.semantic.in_annotation() && checker.semantic.in_runtime_evaluated_annotation() && !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( checker, diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 3cfca8379f..a803e0ac9c 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -200,6 +200,11 @@ pub(crate) const fn is_optional_as_none_in_union_enabled(settings: &LinterSettin 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 pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled( settings: &LinterSettings, diff --git a/crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs b/crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs index 1350b0578d..6612ee086f 100644 --- a/crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs @@ -9,6 +9,7 @@ mod tests { use test_case::test_case; use crate::registry::Rule; + use crate::settings::types::PreviewMode; use crate::test::test_path; use crate::{assert_diagnostics, settings}; 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_preview_generics.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("ok_no_types.py"))] @@ -56,4 +58,19 @@ mod tests { assert_diagnostics!(snapshot, diagnostics); 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(()) + } } diff --git a/crates/ruff_linter/src/rules/flake8_future_annotations/snapshots/ruff_linter__rules__flake8_future_annotations__tests__fa102_no_future_import_uses_preview_generics.py.snap b/crates/ruff_linter/src/rules/flake8_future_annotations/snapshots/ruff_linter__rules__flake8_future_annotations__tests__fa102_no_future_import_uses_preview_generics.py.snap new file mode 100644 index 0000000000..8b775ebb51 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_future_annotations/snapshots/ruff_linter__rules__flake8_future_annotations__tests__fa102_no_future_import_uses_preview_generics.py.snap @@ -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 diff --git a/crates/ruff_linter/src/rules/flake8_future_annotations/snapshots/ruff_linter__rules__flake8_future_annotations__tests__fa102_preview_no_future_import_uses_preview_generics.py.snap b/crates/ruff_linter/src/rules/flake8_future_annotations/snapshots/ruff_linter__rules__flake8_future_annotations__tests__fa102_preview_no_future_import_uses_preview_generics.py.snap new file mode 100644 index 0000000000..c93dcda3dc --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_future_annotations/snapshots/ruff_linter__rules__flake8_future_annotations__tests__fa102_preview_no_future_import_uses_preview_generics.py.snap @@ -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 diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index 01220fea0e..4b30993a15 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -8,11 +8,10 @@ use ruff_python_ast::{ StmtAssign, }; use ruff_python_stdlib::typing::{ - as_pep_585_generic, has_pep_585_generic, is_immutable_generic_type, - is_immutable_non_generic_type, is_immutable_return_type, is_literal_member, - is_mutable_return_type, is_pep_593_generic_member, is_pep_593_generic_type, - is_standard_library_generic, is_standard_library_generic_member, is_standard_library_literal, - is_typed_dict, is_typed_dict_member, + as_pep_585_generic, is_immutable_generic_type, is_immutable_non_generic_type, + is_immutable_return_type, is_literal_member, is_mutable_return_type, is_pep_593_generic_member, + is_pep_593_generic_type, is_standard_library_generic, is_standard_library_generic_member, + is_standard_library_literal, is_typed_dict, is_typed_dict_member, }; use ruff_text_size::Ranged; use smallvec::{SmallVec, smallvec}; @@ -148,14 +147,46 @@ pub fn to_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> Option bool { +pub fn is_pep585_generic( + expr: &Expr, + semantic: &SemanticModel, + include_preview_generics: bool, +) -> bool { semantic .resolve_qualified_name(expr) - .is_some_and(|qualified_name| { - let [module, name] = qualified_name.segments() else { - return false; - }; - has_pep_585_generic(module, name) + .is_some_and(|qualified_name| match qualified_name.segments() { + ["", "dict" | "frozenset" | "list" | "set" | "tuple" | "type"] + | ["collections", "deque" | "defaultdict"] => true, + ["asyncio", "Future" | "Task"] + | ["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, }) } diff --git a/crates/ruff_python_stdlib/src/typing.rs b/crates/ruff_python_stdlib/src/typing.rs index 54dd230931..63d7ccf32e 100644 --- a/crates/ruff_python_stdlib/src/typing.rs +++ b/crates/ruff_python_stdlib/src/typing.rs @@ -354,20 +354,6 @@ pub fn as_pep_585_generic(module: &str, member: &str) -> Option { } } -/// 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. /// /// See: