diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP006_0.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP006_0.py index 106366ec2cc2b..22db1027ad826 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP006_0.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP006_0.py @@ -64,3 +64,22 @@ def f(x: typing.Deque[str]) -> None: def f(x: typing.DefaultDict[str, str]) -> None: ... + + +def f(x: typing.AbstractSet[str]) -> None: + ... + + +def f(x: typing.Pattern[str]) -> None: + ... + + +def f(x: typing.Sequence[str]) -> None: + ... + + +from typing import Collection + + +def f(x: typing.Collection[str]) -> None: + ... diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP006_1.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP006_1.py index 1f56c64011a15..95b8581cb1b8c 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP006_1.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP006_1.py @@ -8,3 +8,19 @@ def f(x: typing.DefaultDict[str, str]) -> None: ... + + +from collections.abc import Set +from typing_extensions import Awaitable + + +def f(x: typing.AbstractSet[str]) -> None: + ... + + +def f(x: Set) -> None: + ... + + +def f(x: Awaitable) -> None: + ... diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index f583089abb19f..0311e8c0f5e16 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -111,6 +111,21 @@ mod tests { Ok(()) } + #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_0.py"))] + #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_1.py"))] + fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("preview__{}", path.to_string_lossy()); + let diagnostics = test_path( + Path::new("pyupgrade").join(path).as_path(), + &settings::LinterSettings { + preview: PreviewMode::Enabled, + ..settings::LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + #[test] fn async_timeout_error_alias_not_applied_py310() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs index 1e08b7e9aa176..644069f81930f 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep585_annotation.rs @@ -14,6 +14,21 @@ use crate::settings::types::PythonVersion; /// Checks for the use of generics that can be replaced with standard library /// variants based on [PEP 585]. /// +/// Under [preview mode](https://docs.astral.sh/ruff/preview), +/// this rule triggers for all replacements listed +/// in [PEP 585]. Otherwise, this rule only triggers for the following +/// commonly occurring instances of modules present in the +/// `typing` or `typing_extensions` package: +/// +/// - `Dict` +/// - `FrozenSet` +/// - `List` +/// - `Set` +/// - `Tuple` +/// - `Type` +/// - `Deque` +/// - `DefaultDict` +/// /// ## Why is this bad? /// [PEP 585] enabled collections in the Python standard library (like `list`) /// to be used as generics directly, instead of importing analogous members @@ -81,6 +96,9 @@ pub(crate) fn use_pep585_annotation( expr: &Expr, replacement: &ModuleMember, ) { + if !checker.settings.preview.is_enabled() && !is_restricted_pep585_generic(replacement) { + return; + } let Some(from) = UnqualifiedName::from_expr(expr) else { return; }; @@ -138,3 +156,11 @@ pub(crate) fn use_pep585_annotation( } checker.diagnostics.push(diagnostic); } + +fn is_restricted_pep585_generic(module_member: &ModuleMember) -> bool { + matches!( + module_member, + ModuleMember::BuiltIn("dict" | "frozenset" | "list" | "set" | "tuple" | "type") + | ModuleMember::Member("collections", "deque" | "defaultdict") + ) +} diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP006_0.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP006_0.py.snap index 58bd2d2497f7f..a35447d1f8871 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP006_0.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP006_0.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs -snapshot_kind: text --- UP006_0.py:4:10: UP006 [*] Use `list` instead of `typing.List` for type annotation | @@ -281,3 +280,5 @@ UP006_0.py:65:10: UP006 [*] Use `collections.defaultdict` instead of `typing.Def 65 |-def f(x: typing.DefaultDict[str, str]) -> None: 66 |+def f(x: defaultdict[str, str]) -> None: 66 67 | ... +67 68 | +68 69 | diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP006_1.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP006_1.py.snap index f262fe049805a..4eaecd69df52c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP006_1.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP006_1.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs -snapshot_kind: text --- UP006_1.py:9:10: UP006 [*] Use `collections.defaultdict` instead of `typing.DefaultDict` for type annotation | @@ -17,3 +16,5 @@ UP006_1.py:9:10: UP006 [*] Use `collections.defaultdict` instead of `typing.Defa 9 |-def f(x: typing.DefaultDict[str, str]) -> None: 9 |+def f(x: defaultdict[str, str]) -> None: 10 10 | ... +11 11 | +12 12 | diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP006_0.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP006_0.py.snap new file mode 100644 index 0000000000000..c05e880c48bbb --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP006_0.py.snap @@ -0,0 +1,370 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP006_0.py:4:10: UP006 [*] Use `list` instead of `typing.List` for type annotation + | +4 | def f(x: typing.List[str]) -> None: + | ^^^^^^^^^^^ UP006 +5 | ... + | + = help: Replace with `list` + +ℹ Safe fix +1 1 | import typing +2 2 | +3 3 | +4 |-def f(x: typing.List[str]) -> None: + 4 |+def f(x: list[str]) -> None: +5 5 | ... +6 6 | +7 7 | + +UP006_0.py:11:10: UP006 [*] Use `list` instead of `List` for type annotation + | +11 | def f(x: List[str]) -> None: + | ^^^^ UP006 +12 | ... + | + = help: Replace with `list` + +ℹ Safe fix +8 8 | from typing import List +9 9 | +10 10 | +11 |-def f(x: List[str]) -> None: + 11 |+def f(x: list[str]) -> None: +12 12 | ... +13 13 | +14 14 | + +UP006_0.py:18:10: UP006 [*] Use `list` instead of `t.List` for type annotation + | +18 | def f(x: t.List[str]) -> None: + | ^^^^^^ UP006 +19 | ... + | + = help: Replace with `list` + +ℹ Safe fix +15 15 | import typing as t +16 16 | +17 17 | +18 |-def f(x: t.List[str]) -> None: + 18 |+def f(x: list[str]) -> None: +19 19 | ... +20 20 | +21 21 | + +UP006_0.py:25:10: UP006 [*] Use `list` instead of `IList` for type annotation + | +25 | def f(x: IList[str]) -> None: + | ^^^^^ UP006 +26 | ... + | + = help: Replace with `list` + +ℹ Safe fix +22 22 | from typing import List as IList +23 23 | +24 24 | +25 |-def f(x: IList[str]) -> None: + 25 |+def f(x: list[str]) -> None: +26 26 | ... +27 27 | +28 28 | + +UP006_0.py:29:11: UP006 [*] Use `list` instead of `List` for type annotation + | +29 | def f(x: "List[str]") -> None: + | ^^^^ UP006 +30 | ... + | + = help: Replace with `list` + +ℹ Safe fix +26 26 | ... +27 27 | +28 28 | +29 |-def f(x: "List[str]") -> None: + 29 |+def f(x: "list[str]") -> None: +30 30 | ... +31 31 | +32 32 | + +UP006_0.py:33:12: UP006 [*] Use `list` instead of `List` for type annotation + | +33 | def f(x: r"List[str]") -> None: + | ^^^^ UP006 +34 | ... + | + = help: Replace with `list` + +ℹ Safe fix +30 30 | ... +31 31 | +32 32 | +33 |-def f(x: r"List[str]") -> None: + 33 |+def f(x: r"list[str]") -> None: +34 34 | ... +35 35 | +36 36 | + +UP006_0.py:37:11: UP006 [*] Use `list` instead of `List` for type annotation + | +37 | def f(x: "List[str]") -> None: + | ^^^^ UP006 +38 | ... + | + = help: Replace with `list` + +ℹ Safe fix +34 34 | ... +35 35 | +36 36 | +37 |-def f(x: "List[str]") -> None: + 37 |+def f(x: "list[str]") -> None: +38 38 | ... +39 39 | +40 40 | + +UP006_0.py:41:13: UP006 [*] Use `list` instead of `List` for type annotation + | +41 | def f(x: """List[str]""") -> None: + | ^^^^ UP006 +42 | ... + | + = help: Replace with `list` + +ℹ Safe fix +38 38 | ... +39 39 | +40 40 | +41 |-def f(x: """List[str]""") -> None: + 41 |+def f(x: """list[str]""") -> None: +42 42 | ... +43 43 | +44 44 | + +UP006_0.py:45:10: UP006 Use `list` instead of `List` for type annotation + | +45 | def f(x: "Li" "st[str]") -> None: + | ^^^^^^^^^^^^^^ UP006 +46 | ... + | + = help: Replace with `list` + +UP006_0.py:49:11: UP006 [*] Use `list` instead of `List` for type annotation + | +49 | def f(x: "List['List[str]']") -> None: + | ^^^^ UP006 +50 | ... + | + = help: Replace with `list` + +ℹ Safe fix +46 46 | ... +47 47 | +48 48 | +49 |-def f(x: "List['List[str]']") -> None: + 49 |+def f(x: "list['List[str]']") -> None: +50 50 | ... +51 51 | +52 52 | + +UP006_0.py:49:17: UP006 [*] Use `list` instead of `List` for type annotation + | +49 | def f(x: "List['List[str]']") -> None: + | ^^^^ UP006 +50 | ... + | + = help: Replace with `list` + +ℹ Safe fix +46 46 | ... +47 47 | +48 48 | +49 |-def f(x: "List['List[str]']") -> None: + 49 |+def f(x: "List['list[str]']") -> None: +50 50 | ... +51 51 | +52 52 | + +UP006_0.py:53:11: UP006 [*] Use `list` instead of `List` for type annotation + | +53 | def f(x: "List['Li' 'st[str]']") -> None: + | ^^^^ UP006 +54 | ... + | + = help: Replace with `list` + +ℹ Safe fix +50 50 | ... +51 51 | +52 52 | +53 |-def f(x: "List['Li' 'st[str]']") -> None: + 53 |+def f(x: "list['Li' 'st[str]']") -> None: +54 54 | ... +55 55 | +56 56 | + +UP006_0.py:53:16: UP006 Use `list` instead of `List` for type annotation + | +53 | def f(x: "List['Li' 'st[str]']") -> None: + | ^^^^^^^^^^^^^^ UP006 +54 | ... + | + = help: Replace with `list` + +UP006_0.py:57:10: UP006 Use `list` instead of `List` for type annotation + | +57 | def f(x: "Li" "st['List[str]']") -> None: + | ^^^^^^^^^^^^^^^^^^^^^^ UP006 +58 | ... + | + = help: Replace with `list` + +UP006_0.py:57:10: UP006 Use `list` instead of `List` for type annotation + | +57 | def f(x: "Li" "st['List[str]']") -> None: + | ^^^^^^^^^^^^^^^^^^^^^^ UP006 +58 | ... + | + = help: Replace with `list` + +UP006_0.py:61:10: UP006 [*] Use `collections.deque` instead of `typing.Deque` for type annotation + | +61 | def f(x: typing.Deque[str]) -> None: + | ^^^^^^^^^^^^ UP006 +62 | ... + | + = help: Replace with `collections.deque` + +ℹ Safe fix +20 20 | +21 21 | +22 22 | from typing import List as IList + 23 |+from collections import deque +23 24 | +24 25 | +25 26 | def f(x: IList[str]) -> None: +-------------------------------------------------------------------------------- +58 59 | ... +59 60 | +60 61 | +61 |-def f(x: typing.Deque[str]) -> None: + 62 |+def f(x: deque[str]) -> None: +62 63 | ... +63 64 | +64 65 | + +UP006_0.py:65:10: UP006 [*] Use `collections.defaultdict` instead of `typing.DefaultDict` for type annotation + | +65 | def f(x: typing.DefaultDict[str, str]) -> None: + | ^^^^^^^^^^^^^^^^^^ UP006 +66 | ... + | + = help: Replace with `collections.defaultdict` + +ℹ Safe fix +20 20 | +21 21 | +22 22 | from typing import List as IList + 23 |+from collections import defaultdict +23 24 | +24 25 | +25 26 | def f(x: IList[str]) -> None: +-------------------------------------------------------------------------------- +62 63 | ... +63 64 | +64 65 | +65 |-def f(x: typing.DefaultDict[str, str]) -> None: + 66 |+def f(x: defaultdict[str, str]) -> None: +66 67 | ... +67 68 | +68 69 | + +UP006_0.py:69:10: UP006 [*] Use `collections.abc.Set` instead of `typing.AbstractSet` for type annotation + | +69 | def f(x: typing.AbstractSet[str]) -> None: + | ^^^^^^^^^^^^^^^^^^ UP006 +70 | ... + | + = help: Replace with `collections.abc.Set` + +ℹ Safe fix +20 20 | +21 21 | +22 22 | from typing import List as IList + 23 |+from collections.abc import Set +23 24 | +24 25 | +25 26 | def f(x: IList[str]) -> None: +-------------------------------------------------------------------------------- +66 67 | ... +67 68 | +68 69 | +69 |-def f(x: typing.AbstractSet[str]) -> None: + 70 |+def f(x: Set[str]) -> None: +70 71 | ... +71 72 | +72 73 | + +UP006_0.py:73:10: UP006 [*] Use `re.Pattern` instead of `typing.Pattern` for type annotation + | +73 | def f(x: typing.Pattern[str]) -> None: + | ^^^^^^^^^^^^^^ UP006 +74 | ... + | + = help: Replace with `re.Pattern` + +ℹ Safe fix +20 20 | +21 21 | +22 22 | from typing import List as IList + 23 |+from re import Pattern +23 24 | +24 25 | +25 26 | def f(x: IList[str]) -> None: +-------------------------------------------------------------------------------- +70 71 | ... +71 72 | +72 73 | +73 |-def f(x: typing.Pattern[str]) -> None: + 74 |+def f(x: Pattern[str]) -> None: +74 75 | ... +75 76 | +76 77 | + +UP006_0.py:77:10: UP006 [*] Use `collections.abc.Sequence` instead of `typing.Sequence` for type annotation + | +77 | def f(x: typing.Sequence[str]) -> None: + | ^^^^^^^^^^^^^^^ UP006 +78 | ... + | + = help: Replace with `collections.abc.Sequence` + +ℹ Safe fix +20 20 | +21 21 | +22 22 | from typing import List as IList + 23 |+from collections.abc import Sequence +23 24 | +24 25 | +25 26 | def f(x: IList[str]) -> None: +-------------------------------------------------------------------------------- +74 75 | ... +75 76 | +76 77 | +77 |-def f(x: typing.Sequence[str]) -> None: + 78 |+def f(x: Sequence[str]) -> None: +78 79 | ... +79 80 | +80 81 | + +UP006_0.py:84:10: UP006 Use `collections.abc.Collection` instead of `typing.Collection` for type annotation + | +84 | def f(x: typing.Collection[str]) -> None: + | ^^^^^^^^^^^^^^^^^ UP006 +85 | ... + | + = help: Replace with `collections.abc.Collection` diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP006_1.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP006_1.py.snap new file mode 100644 index 0000000000000..2871a7ee91e25 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview__UP006_1.py.snap @@ -0,0 +1,46 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP006_1.py:9:10: UP006 [*] Use `collections.defaultdict` instead of `typing.DefaultDict` for type annotation + | + 9 | def f(x: typing.DefaultDict[str, str]) -> None: + | ^^^^^^^^^^^^^^^^^^ UP006 +10 | ... + | + = help: Replace with `collections.defaultdict` + +ℹ Safe fix +6 6 | from collections import defaultdict +7 7 | +8 8 | +9 |-def f(x: typing.DefaultDict[str, str]) -> None: + 9 |+def f(x: defaultdict[str, str]) -> None: +10 10 | ... +11 11 | +12 12 | + +UP006_1.py:17:10: UP006 [*] Use `collections.abc.Set` instead of `typing.AbstractSet` for type annotation + | +17 | def f(x: typing.AbstractSet[str]) -> None: + | ^^^^^^^^^^^^^^^^^^ UP006 +18 | ... + | + = help: Replace with `collections.abc.Set` + +ℹ Safe fix +14 14 | from typing_extensions import Awaitable +15 15 | +16 16 | +17 |-def f(x: typing.AbstractSet[str]) -> None: + 17 |+def f(x: Set[str]) -> None: +18 18 | ... +19 19 | +20 20 | + +UP006_1.py:25:10: UP006 Use `collections.abc.Awaitable` instead of `Awaitable` for type annotation + | +25 | def f(x: Awaitable) -> None: + | ^^^^^^^^^ UP006 +26 | ... + | + = help: Replace with `collections.abc.Awaitable` diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index b346390a8908c..9686191826caf 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -108,10 +108,7 @@ pub fn to_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> Option Option { - match (module, member) { - ("typing", "Dict") => Some(("", "dict")), - ("typing", "FrozenSet") => Some(("", "frozenset")), - ("typing", "List") => Some(("", "list")), - ("typing", "Set") => Some(("", "set")), - ("typing", "Tuple") => Some(("", "tuple")), - ("typing", "Type") => Some(("", "type")), - ("typing_extensions", "Type") => Some(("", "type")), - ("typing", "Deque") => Some(("collections", "deque")), - ("typing_extensions", "Deque") => Some(("collections", "deque")), - ("typing", "DefaultDict") => Some(("collections", "defaultdict")), - ("typing_extensions", "DefaultDict") => Some(("collections", "defaultdict")), +pub fn as_pep_585_generic(call_path: &[&str]) -> Option { + match call_path { + // Builtins + ["typing" | "typing_extensions", "Tuple"] => Some(("", "tuple")), + ["typing" | "typing_extensions", "List"] => Some(("", "list")), + ["typing" | "typing_extensions", "Dict"] => Some(("", "dict")), + ["typing" | "typing_extensions", "Set"] => Some(("", "set")), + ["typing" | "typing_extensions", "FrozenSet"] => Some(("", "frozenset")), + ["typing" | "typing_extensions", "Type"] => Some(("", "type")), + + // collections + ["typing" | "typing_extensions", "Deque"] => Some(("collections", "deque")), + ["typing" | "typing_extensions", "DefaultDict"] => Some(("collections", "defaultdict")), + ["typing" | "typing_extensions", "OrderedDict"] => Some(("collections", "OrderedDict")), + ["typing" | "typing_extensions", "Counter"] => Some(("collections", "Counter")), + ["typing" | "typing_extensions", "ChainMap"] => Some(("collections", "ChainMap")), + + // collections.abc + ["typing" | "typing_extensions", "Awaitable"] => Some(("collections.abc", "Awaitable")), + ["typing" | "typing_extensions", "Coroutine"] => Some(("collections.abc", "Coroutine")), + ["typing" | "typing_extensions", "AsyncIterable"] => { + Some(("collections.abc", "AsyncIterable")) + } + ["typing" | "typing_extensions", "AsyncGenerator"] => { + Some(("collections.abc", "AsyncGenerator")) + } + ["typing" | "typing_extensions", "Iterable"] => Some(("collections.abc", "Iterable")), + ["typing" | "typing_extensions", "Iterator"] => Some(("collections.abc", "Iterator")), + ["typing" | "typing_extensions", "Generator"] => Some(("collections.abc", "Generator")), + ["typing" | "typing_extensions", "Reversible"] => Some(("collections.abc", "Reversible")), + ["typing" | "typing_extensions", "Container"] => Some(("collections.abc", "Container")), + ["typing" | "typing_extensions", "Collection"] => Some(("collections.abc", "Collection")), + ["typing" | "typing_extensions", "Callable"] => Some(("collections.abc", "Callable")), + ["typing" | "typing_extensions", "AbstractSet"] => Some(("collections.abc", "Set")), + ["typing" | "typing_extensions", "MutableSet"] => Some(("collections.abc", "MutableSet")), + ["typing" | "typing_extensions", "Mapping"] => Some(("collections.abc", "Mapping")), + ["typing" | "typing_extensions", "MutableMapping"] => { + Some(("collections.abc", "MutableMapping")) + } + ["typing" | "typing_extensions", "Sequence"] => Some(("collections.abc", "Sequence")), + ["typing" | "typing_extensions", "MutableSequence"] => { + Some(("collections.abc", "MutableSequence")) + } + ["typing" | "typing_extensions", "ByteString"] => Some(("collections.abc", "ByteString")), + ["typing" | "typing_extensions", "MappingView"] => Some(("collections.abc", "MappingView")), + ["typing" | "typing_extensions", "KeysView"] => Some(("collections.abc", "KeysView")), + ["typing" | "typing_extensions", "ItemsView"] => Some(("collections.abc", "ItemsView")), + ["typing" | "typing_extensions", "ValuesView"] => Some(("collections.abc", "ValuesView")), + + // contextlib + ["typing" | "typing_extensions", "ContextManager"] => { + Some(("contextlib", "AbstractContextManager")) + } + ["typing" | "typing_extensions", "AsyncContextManager"] => { + Some(("contextlib", "AbstractAsyncContextManager")) + } + + // re + ["typing" | "typing_extensions", "Pattern"] => Some(("re", "Pattern")), + ["typing" | "typing_extensions", "Match"] => Some(("re", "Match")), + ["typing" | "typing_extensions", "re", "Pattern"] => Some(("re", "Pattern")), + ["typing" | "typing_extensions", "re", "Match"] => Some(("re", "Match")), + _ => None, } } @@ -347,8 +397,39 @@ pub fn has_pep_585_generic(module: &str, member: &str) -> bool { // the last element in each pattern, and de-duplicating the values. matches!( (module, member), - ("", "dict" | "frozenset" | "list" | "set" | "tuple" | "type") - | ("collections", "deque" | "defaultdict") + ("", "tuple" | "list" | "dict" | "set" | "frozenset" | "type") + | ( + "collections", + "deque" | "defaultdict" | "OrderedDict" | "Counter" | "ChainMap" + ) + | ( + "collections.abc", + "Awaitable" + | "Coroutine" + | "Iterable" + | "Iterator" + | "Generator" + | "Reversible" + | "Container" + | "Collection" + | "Callable" + | "Set" + | "MutableSet" + | "Mapping" + | "MutableMapping" + | "Sequence" + | "MutableSequence" + | "ByteString" + | "MappingView" + | "KeysView" + | "ItemsView" + | "ValuesView" + ) + | ( + "contextlib", + "AbstractContextManager" | "AbstractAsyncContextManager" + ) + | ("re", "Pattern" | "Match") ) }