Skip to content

Commit 410902f

Browse files
authored
[pyupgrade] Fix handling of typing.{io,re} (UP035) (#23131)
## Summary <!-- What's the purpose of the change? What does it do, and why? --> Back in Python 3.5, the docs referred to `typing.io` as the primary location for `IO`, `TextIO`, and `BinaryIO` and `typing.re` as the primary location for `Pattern` and `Match`: - https://docs.python.org/3.5/library/typing.html#typing.io - https://docs.python.org/3.5/library/typing.html#typing.re In Python 3.6, reference to `typing.io` and `typing.re` disappeared and these types were importable directly from `typing`: - https://docs.python.org/3.6/library/typing.html#typing.IO - https://docs.python.org/3.6/library/typing.html#typing.Pattern In Python 3.9, the `typing.io` and `typing.re` namespaces were deprecated pending removal in Python 3.12. In addition, `typing.Pattern` and `typing.Match` were deprecated in favour of `re.Pattern` and `re.Match`: - https://docs.python.org/3.9/library/typing.html#typing.IO - https://docs.python.org/3.9/library/typing.html#typing.Pattern Although interestingly it implies that the deprecation of the namespaces was from Python 3.8. The pending removal version for the namespaces was updated from 3.12 to 3.13 as the deprecation warning was only in place from 3.11 - that update was backported through 3.10: - https://docs.python.org/3.10/library/typing.html#typing.IO - https://docs.python.org/3.10/library/typing.html#typing.Pattern The namespaces were removed in Python 3.13: - https://docs.python.org/3.13/library/typing.html#typing.IO - https://docs.python.org/3.13/library/typing.html#typing.Pattern On this basis, it seems we could update `UP035` for the `typing.io` and `typing.re` namespaces to target as far back as Python 3.6 as they weren't even mentioned in the docs for Python 3.6 to 3.8 and only mentioned again when soft deprecated in Python 3.9. The versions of these types in the main `typing` module have been available the whole time. As ruff only targets 3.7+, let's go for 3.7. ## Test Plan <!-- How was it tested? --> Updated snapshots.
1 parent 729610a commit 410902f

File tree

3 files changed

+128
-3
lines changed

3 files changed

+128
-3
lines changed

crates/ruff_linter/resources/test/fixtures/pyupgrade/UP035.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,18 @@
117117
from typing_extensions import is_typeddict
118118
# https://github.com/astral-sh/ruff/pull/15800#pullrequestreview-2580704217
119119
from typing_extensions import TypedDict
120+
121+
# UP035 on py37+ only
122+
from typing.io import BinaryIO
123+
124+
# UP035 on py37+ only
125+
from typing.io import IO
126+
127+
# UP035 on py37+ only
128+
from typing.io import TextIO
129+
130+
# UP035 on py37+ only
131+
from typing.re import Match
132+
133+
# UP035 on py37+ only
134+
from typing.re import Pattern

crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ fn is_relevant_module(module: &str) -> bool {
109109
| "mypy_extensions"
110110
| "typing_extensions"
111111
| "typing"
112+
| "typing.io"
112113
| "typing.re"
113114
| "backports.strenum"
114115
)
@@ -218,6 +219,14 @@ const TYPING_EXTENSIONS_TO_TYPING_37: &[&str] = &[
218219
// "NamedTuple",
219220
];
220221

222+
// Members of `typing.io` that were moved to `typing`.
223+
// Note that the `typing.io` namespace has pretty much always been unnecessary.
224+
const TYPING_IO_TO_TYPING_37: &[&str] = &["BinaryIO", "IO", "TextIO"];
225+
226+
// Members of `typing.re` that were moved to `typing`.
227+
// Note that the `typing.re` namespace has pretty much always been unnecessary.
228+
const TYPING_RE_TO_TYPING_37: &[&str] = &["Match", "Pattern"];
229+
221230
// Python 3.8+
222231

223232
// Members of `mypy_extensions` that were moved to `typing`.
@@ -268,7 +277,7 @@ const TYPING_TO_COLLECTIONS_ABC_39: &[&str] = &[
268277
// Members of `typing` that were moved to `collections`.
269278
const TYPING_TO_COLLECTIONS_39: &[&str] = &["ChainMap", "Counter", "OrderedDict"];
270279

271-
// Members of `typing` that were moved to `typing.re`.
280+
// Members of `typing` that were moved to `re`.
272281
const TYPING_TO_RE_39: &[&str] = &["Match", "Pattern"];
273282

274283
// Members of `typing.re` that were moved to `re`.
@@ -592,11 +601,22 @@ impl<'a> ImportReplacer<'a> {
592601
operations.push(operation);
593602
}
594603
}
595-
"typing.re" if self.version >= PythonVersion::PY39 => {
596-
if let Some(operation) = self.try_replace(TYPING_RE_TO_RE_39, "re") {
604+
"typing.io" if self.version >= PythonVersion::PY37 => {
605+
if let Some(operation) = self.try_replace(TYPING_IO_TO_TYPING_37, "typing") {
597606
operations.push(operation);
598607
}
599608
}
609+
"typing.re" => {
610+
if self.version >= PythonVersion::PY39 {
611+
if let Some(operation) = self.try_replace(TYPING_RE_TO_RE_39, "re") {
612+
operations.push(operation);
613+
}
614+
} else if self.version >= PythonVersion::PY37 {
615+
if let Some(operation) = self.try_replace(TYPING_RE_TO_TYPING_37, "typing") {
616+
operations.push(operation);
617+
}
618+
}
619+
}
600620
"backports.strenum" if self.version >= PythonVersion::PY311 => {
601621
if let Some(operation) = self.try_replace(BACKPORTS_STR_ENUM_TO_ENUM_311, "enum") {
602622
operations.push(operation);

crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP035.py.snap

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,3 +1163,93 @@ help: Import from `typing`
11631163
115 |
11641164
116 | # https://github.com/astral-sh/ruff/issues/15780
11651165
117 | from typing_extensions import is_typeddict
1166+
1167+
UP035 [*] Import from `typing` instead: `BinaryIO`
1168+
--> UP035.py:122:1
1169+
|
1170+
121 | # UP035 on py37+ only
1171+
122 | from typing.io import BinaryIO
1172+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1173+
123 |
1174+
124 | # UP035 on py37+ only
1175+
|
1176+
help: Import from `typing`
1177+
119 | from typing_extensions import TypedDict
1178+
120 |
1179+
121 | # UP035 on py37+ only
1180+
- from typing.io import BinaryIO
1181+
122 + from typing import BinaryIO
1182+
123 |
1183+
124 | # UP035 on py37+ only
1184+
125 | from typing.io import IO
1185+
1186+
UP035 [*] Import from `typing` instead: `IO`
1187+
--> UP035.py:125:1
1188+
|
1189+
124 | # UP035 on py37+ only
1190+
125 | from typing.io import IO
1191+
| ^^^^^^^^^^^^^^^^^^^^^^^^
1192+
126 |
1193+
127 | # UP035 on py37+ only
1194+
|
1195+
help: Import from `typing`
1196+
122 | from typing.io import BinaryIO
1197+
123 |
1198+
124 | # UP035 on py37+ only
1199+
- from typing.io import IO
1200+
125 + from typing import IO
1201+
126 |
1202+
127 | # UP035 on py37+ only
1203+
128 | from typing.io import TextIO
1204+
1205+
UP035 [*] Import from `typing` instead: `TextIO`
1206+
--> UP035.py:128:1
1207+
|
1208+
127 | # UP035 on py37+ only
1209+
128 | from typing.io import TextIO
1210+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1211+
129 |
1212+
130 | # UP035 on py37+ only
1213+
|
1214+
help: Import from `typing`
1215+
125 | from typing.io import IO
1216+
126 |
1217+
127 | # UP035 on py37+ only
1218+
- from typing.io import TextIO
1219+
128 + from typing import TextIO
1220+
129 |
1221+
130 | # UP035 on py37+ only
1222+
131 | from typing.re import Match
1223+
1224+
UP035 [*] Import from `re` instead: `Match`
1225+
--> UP035.py:131:1
1226+
|
1227+
130 | # UP035 on py37+ only
1228+
131 | from typing.re import Match
1229+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1230+
132 |
1231+
133 | # UP035 on py37+ only
1232+
|
1233+
help: Import from `re`
1234+
128 | from typing.io import TextIO
1235+
129 |
1236+
130 | # UP035 on py37+ only
1237+
- from typing.re import Match
1238+
131 + from re import Match
1239+
132 |
1240+
133 | # UP035 on py37+ only
1241+
134 | from typing.re import Pattern
1242+
1243+
UP035 [*] Import from `re` instead: `Pattern`
1244+
--> UP035.py:134:1
1245+
|
1246+
133 | # UP035 on py37+ only
1247+
134 | from typing.re import Pattern
1248+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1249+
|
1250+
help: Import from `re`
1251+
131 | from typing.re import Match
1252+
132 |
1253+
133 | # UP035 on py37+ only
1254+
- from typing.re import Pattern
1255+
134 + from re import Pattern

0 commit comments

Comments
 (0)