Skip to content

Commit 685d11a

Browse files
Mark repeated-isinstance-calls as unsafe on Python 3.10 and later (#11622)
## Summary Closes #11616.
1 parent dcabd04 commit 685d11a

File tree

3 files changed

+25
-16
lines changed

3 files changed

+25
-16
lines changed

crates/ruff_linter/src/rules/pylint/rules/repeated_isinstance_calls.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
44

55
use crate::fix::edits::pad;
66
use crate::fix::snippet::SourceCodeSnippet;
7-
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
7+
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
88
use ruff_macros::{derive_message_formats, violation};
99
use ruff_python_ast::hashable::HashableExpr;
1010
use ruff_text_size::Ranged;
@@ -20,6 +20,15 @@ use crate::settings::types::PythonVersion;
2020
/// Repeated `isinstance` calls on the same object can be merged into a
2121
/// single call.
2222
///
23+
/// ## Fix safety
24+
/// This rule's fix is marked as unsafe on Python 3.10 and later, as combining
25+
/// multiple `isinstance` calls with a binary operator (`|`) will fail at
26+
/// runtime if any of the operands are themselves tuples.
27+
///
28+
/// For example, given `TYPES = (dict, list)`, then
29+
/// `isinstance(None, TYPES | set | float)` will raise a `TypeError` at runtime,
30+
/// while `isinstance(None, set | float)` will not.
31+
///
2332
/// ## Example
2433
/// ```python
2534
/// def is_number(x):
@@ -130,10 +139,14 @@ pub(crate) fn repeated_isinstance_calls(
130139
},
131140
expr.range(),
132141
);
133-
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
134-
pad(call, expr.range(), checker.locator()),
135-
expr.range(),
136-
)));
142+
diagnostic.set_fix(Fix::applicable_edit(
143+
Edit::range_replacement(pad(call, expr.range(), checker.locator()), expr.range()),
144+
if checker.settings.target_version >= PythonVersion::Py310 {
145+
Applicability::Unsafe
146+
} else {
147+
Applicability::Safe
148+
},
149+
));
137150
checker.diagnostics.push(diagnostic);
138151
}
139152
}

crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR1701_repeated_isinstance_calls.py.snap

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repeated_isinstance_calls.py:15:8: PLR1701 [*] Merge `isinstance` calls: `isinst
1111
|
1212
= help: Replace with `isinstance(var[3], float | int)`
1313

14-
Safe fix
14+
Unsafe fix
1515
12 12 | result = isinstance(var[2], (int, float))
1616
13 13 |
1717
14 14 | # not merged
@@ -32,7 +32,7 @@ repeated_isinstance_calls.py:17:14: PLR1701 [*] Merge `isinstance` calls: `isins
3232
|
3333
= help: Replace with `isinstance(var[4], float | int)`
3434

35-
Safe fix
35+
Unsafe fix
3636
14 14 | # not merged
3737
15 15 | if isinstance(var[3], int) or isinstance(var[3], float) or isinstance(var[3], list) and True: # [consider-merging-isinstance]
3838
16 16 | pass
@@ -53,7 +53,7 @@ repeated_isinstance_calls.py:19:14: PLR1701 [*] Merge `isinstance` calls: `isins
5353
|
5454
= help: Replace with `isinstance(var[5], float | int)`
5555

56-
Safe fix
56+
Unsafe fix
5757
16 16 | pass
5858
17 17 | result = isinstance(var[4], int) or isinstance(var[4], float) or isinstance(var[5], list) and False # [consider-merging-isinstance]
5959
18 18 |
@@ -73,7 +73,7 @@ repeated_isinstance_calls.py:23:14: PLR1701 [*] Merge `isinstance` calls: `isins
7373
|
7474
= help: Replace with `isinstance(var[10], list | str)`
7575

76-
Safe fix
76+
Unsafe fix
7777
20 20 |
7878
21 21 | inferred_isinstance = isinstance
7979
22 22 | result = inferred_isinstance(var[6], int) or inferred_isinstance(var[6], float) or inferred_isinstance(var[6], list) and False # [consider-merging-isinstance]
@@ -94,7 +94,7 @@ repeated_isinstance_calls.py:24:14: PLR1701 [*] Merge `isinstance` calls: `isins
9494
|
9595
= help: Replace with `isinstance(var[11], float | int)`
9696

97-
Safe fix
97+
Unsafe fix
9898
21 21 | inferred_isinstance = isinstance
9999
22 22 | result = inferred_isinstance(var[6], int) or inferred_isinstance(var[6], float) or inferred_isinstance(var[6], list) and False # [consider-merging-isinstance]
100100
23 23 | result = isinstance(var[10], str) or isinstance(var[10], int) and var[8] * 14 or isinstance(var[10], float) and var[5] * 14.4 or isinstance(var[10], list) # [consider-merging-isinstance]
@@ -114,7 +114,7 @@ repeated_isinstance_calls.py:30:14: PLR1701 [*] Merge `isinstance` calls: `isins
114114
|
115115
= help: Replace with `isinstance(var[12], float | int | list)`
116116

117-
Safe fix
117+
Unsafe fix
118118
27 27 | result = isinstance()
119119
28 28 |
120120
29 29 | # Combination merged and not merged
@@ -133,12 +133,10 @@ repeated_isinstance_calls.py:42:3: PLR1701 [*] Merge `isinstance` calls: `isinst
133133
|
134134
= help: Replace with `isinstance(self.k, float | int)`
135135

136-
Safe fix
136+
Unsafe fix
137137
39 39 |
138138
40 40 |
139139
41 41 | # Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722460483
140140
42 |-if(isinstance(self.k, int)) or (isinstance(self.k, float)):
141141
42 |+if isinstance(self.k, float | int):
142142
43 43 | ...
143-
144-

crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__repeated_isinstance_calls.snap

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,3 @@ repeated_isinstance_calls.py:42:3: PLR1701 [*] Merge `isinstance` calls: `isinst
140140
42 |-if(isinstance(self.k, int)) or (isinstance(self.k, float)):
141141
42 |+if isinstance(self.k, (float, int)):
142142
43 43 | ...
143-
144-

0 commit comments

Comments
 (0)