diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP035.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP035.py index b9c3892a9b381..6b9073a39db27 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP035.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP035.py @@ -117,3 +117,18 @@ from typing_extensions import is_typeddict # https://github.com/astral-sh/ruff/pull/15800#pullrequestreview-2580704217 from typing_extensions import TypedDict + +# UP035 on py37+ only +from typing.io import BinaryIO + +# UP035 on py37+ only +from typing.io import IO + +# UP035 on py37+ only +from typing.io import TextIO + +# UP035 on py37+ only +from typing.re import Match + +# UP035 on py37+ only +from typing.re import Pattern diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs index 12e1b1e0393eb..86c05b9e86a99 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs @@ -109,6 +109,7 @@ fn is_relevant_module(module: &str) -> bool { | "mypy_extensions" | "typing_extensions" | "typing" + | "typing.io" | "typing.re" | "backports.strenum" ) @@ -218,6 +219,14 @@ const TYPING_EXTENSIONS_TO_TYPING_37: &[&str] = &[ // "NamedTuple", ]; +// Members of `typing.io` that were moved to `typing`. +// Note that the `typing.io` namespace has pretty much always been unnecessary. +const TYPING_IO_TO_TYPING_37: &[&str] = &["BinaryIO", "IO", "TextIO"]; + +// Members of `typing.re` that were moved to `typing`. +// Note that the `typing.re` namespace has pretty much always been unnecessary. +const TYPING_RE_TO_TYPING_37: &[&str] = &["Match", "Pattern"]; + // Python 3.8+ // Members of `mypy_extensions` that were moved to `typing`. @@ -268,7 +277,7 @@ const TYPING_TO_COLLECTIONS_ABC_39: &[&str] = &[ // Members of `typing` that were moved to `collections`. const TYPING_TO_COLLECTIONS_39: &[&str] = &["ChainMap", "Counter", "OrderedDict"]; -// Members of `typing` that were moved to `typing.re`. +// Members of `typing` that were moved to `re`. const TYPING_TO_RE_39: &[&str] = &["Match", "Pattern"]; // Members of `typing.re` that were moved to `re`. @@ -592,11 +601,22 @@ impl<'a> ImportReplacer<'a> { operations.push(operation); } } - "typing.re" if self.version >= PythonVersion::PY39 => { - if let Some(operation) = self.try_replace(TYPING_RE_TO_RE_39, "re") { + "typing.io" if self.version >= PythonVersion::PY37 => { + if let Some(operation) = self.try_replace(TYPING_IO_TO_TYPING_37, "typing") { operations.push(operation); } } + "typing.re" => { + if self.version >= PythonVersion::PY39 { + if let Some(operation) = self.try_replace(TYPING_RE_TO_RE_39, "re") { + operations.push(operation); + } + } else if self.version >= PythonVersion::PY37 { + if let Some(operation) = self.try_replace(TYPING_RE_TO_TYPING_37, "typing") { + operations.push(operation); + } + } + } "backports.strenum" if self.version >= PythonVersion::PY311 => { if let Some(operation) = self.try_replace(BACKPORTS_STR_ENUM_TO_ENUM_311, "enum") { operations.push(operation); diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP035.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP035.py.snap index ac901b5d6651c..66503a3ea07f0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP035.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP035.py.snap @@ -1163,3 +1163,93 @@ help: Import from `typing` 115 | 116 | # https://github.com/astral-sh/ruff/issues/15780 117 | from typing_extensions import is_typeddict + +UP035 [*] Import from `typing` instead: `BinaryIO` + --> UP035.py:122:1 + | +121 | # UP035 on py37+ only +122 | from typing.io import BinaryIO + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +123 | +124 | # UP035 on py37+ only + | +help: Import from `typing` +119 | from typing_extensions import TypedDict +120 | +121 | # UP035 on py37+ only + - from typing.io import BinaryIO +122 + from typing import BinaryIO +123 | +124 | # UP035 on py37+ only +125 | from typing.io import IO + +UP035 [*] Import from `typing` instead: `IO` + --> UP035.py:125:1 + | +124 | # UP035 on py37+ only +125 | from typing.io import IO + | ^^^^^^^^^^^^^^^^^^^^^^^^ +126 | +127 | # UP035 on py37+ only + | +help: Import from `typing` +122 | from typing.io import BinaryIO +123 | +124 | # UP035 on py37+ only + - from typing.io import IO +125 + from typing import IO +126 | +127 | # UP035 on py37+ only +128 | from typing.io import TextIO + +UP035 [*] Import from `typing` instead: `TextIO` + --> UP035.py:128:1 + | +127 | # UP035 on py37+ only +128 | from typing.io import TextIO + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +129 | +130 | # UP035 on py37+ only + | +help: Import from `typing` +125 | from typing.io import IO +126 | +127 | # UP035 on py37+ only + - from typing.io import TextIO +128 + from typing import TextIO +129 | +130 | # UP035 on py37+ only +131 | from typing.re import Match + +UP035 [*] Import from `re` instead: `Match` + --> UP035.py:131:1 + | +130 | # UP035 on py37+ only +131 | from typing.re import Match + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +132 | +133 | # UP035 on py37+ only + | +help: Import from `re` +128 | from typing.io import TextIO +129 | +130 | # UP035 on py37+ only + - from typing.re import Match +131 + from re import Match +132 | +133 | # UP035 on py37+ only +134 | from typing.re import Pattern + +UP035 [*] Import from `re` instead: `Pattern` + --> UP035.py:134:1 + | +133 | # UP035 on py37+ only +134 | from typing.re import Pattern + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: Import from `re` +131 | from typing.re import Match +132 | +133 | # UP035 on py37+ only + - from typing.re import Pattern +134 + from re import Pattern