Skip to content

Commit 8314c8b

Browse files
authored
[typing] Add find_assigned_value helper func to typing.rs to retrieve value of a given variable id (#8583)
## Summary Adds `find_assigned_value` a function which gets the `&Expr` assigned to a given `id` if one exists in the semantic model. Open TODOs: - [ ] Handle `binding.kind.is_unpacked_assignment()`: I am bit confused by this one. The snippet from its documentation does not appear to be counted as an unpacked assignment and the only ones I could find for which that was true were invalid Python like: ```python x, y = 1 ``` - [ ] How to handle AugAssign. Can we combine statements like: ```python (a, b) = [(1, 2, 3), (4,)] a += (6, 7) ``` to get the full value for a? Code currently just returns `None` for these assign types - [ ] Multi target assigns ```python m_c = (m_d, m_e) = (0, 0) trio.sleep(m_c) # OK trio.sleep(m_d) # TRIO115 trio.sleep(m_e) # TRIO115 ``` ## Test Plan Used the function in two rules: - `TRIO115` - `PERF101` Expanded both their fixtures for explicit multi target check
1 parent cb201bc commit 8314c8b

File tree

7 files changed

+382
-85
lines changed

7 files changed

+382
-85
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO115.py

+22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,28 @@ async def func():
1919
bar = "bar"
2020
trio.sleep(bar)
2121

22+
x, y = 0, 2000
23+
trio.sleep(x) # TRIO115
24+
trio.sleep(y) # OK
25+
26+
(a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
27+
trio.sleep(c) # TRIO115
28+
trio.sleep(d) # OK
29+
trio.sleep(e) # TRIO115
30+
31+
m_x, m_y = 0
32+
trio.sleep(m_y) # TRIO115
33+
trio.sleep(m_x) # TRIO115
34+
35+
m_a = m_b = 0
36+
trio.sleep(m_a) # TRIO115
37+
trio.sleep(m_b) # TRIO115
38+
39+
m_c = (m_d, m_e) = (0, 0)
40+
trio.sleep(m_c) # OK
41+
trio.sleep(m_d) # TRIO115
42+
trio.sleep(m_e) # TRIO115
43+
2244

2345
def func():
2446
trio.run(trio.sleep(0)) # TRIO115

crates/ruff_linter/resources/test/fixtures/perflint/PERF101.py

+5
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,8 @@
6363

6464
for i in list(foo_set): # Ok
6565
foo_set.append(i + 1)
66+
67+
x, y, nested_tuple = (1, 2, (3, 4, 5))
68+
69+
for i in list(nested_tuple): # PERF101
70+
pass

crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs

+10-25
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
22
use ruff_macros::{derive_message_formats, violation};
3-
use ruff_python_ast::Stmt;
43
use ruff_python_ast::{self as ast, Expr, ExprCall, Int};
4+
use ruff_python_semantic::analyze::typing::find_assigned_value;
55
use ruff_text_size::Ranged;
66

77
use crate::checkers::ast::Checker;
@@ -71,30 +71,15 @@ pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) {
7171
}
7272
}
7373
Expr::Name(ast::ExprName { id, .. }) => {
74-
let scope = checker.semantic().current_scope();
75-
if let Some(binding_id) = scope.get(id) {
76-
let binding = checker.semantic().binding(binding_id);
77-
if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() {
78-
if let Some(parent_id) = binding.source {
79-
let parent = checker.semantic().statement(parent_id);
80-
if let Stmt::Assign(ast::StmtAssign { value, .. })
81-
| Stmt::AnnAssign(ast::StmtAnnAssign {
82-
value: Some(value), ..
83-
})
84-
| Stmt::AugAssign(ast::StmtAugAssign { value, .. }) = parent
85-
{
86-
let Expr::NumberLiteral(ast::ExprNumberLiteral { value: num, .. }) =
87-
value.as_ref()
88-
else {
89-
return;
90-
};
91-
let Some(int) = num.as_int() else { return };
92-
if *int != Int::ZERO {
93-
return;
94-
}
95-
}
96-
}
97-
}
74+
let Some(value) = find_assigned_value(id, checker.semantic()) else {
75+
return;
76+
};
77+
let Expr::NumberLiteral(ast::ExprNumberLiteral { value: num, .. }) = value else {
78+
return;
79+
};
80+
let Some(int) = num.as_int() else { return };
81+
if *int != Int::ZERO {
82+
return;
9883
}
9984
}
10085
_ => return,

crates/ruff_linter/src/rules/flake8_trio/snapshots/ruff_linter__rules__flake8_trio__tests__TRIO115_TRIO115.py.snap

+213-34
Original file line numberDiff line numberDiff line change
@@ -85,51 +85,230 @@ TRIO115.py:17:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s
8585
19 19 | bar = "bar"
8686
20 20 | trio.sleep(bar)
8787

88-
TRIO115.py:31:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
88+
TRIO115.py:23:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
8989
|
90-
30 | def func():
91-
31 | sleep(0) # TRIO115
92-
| ^^^^^^^^ TRIO115
90+
22 | x, y = 0, 2000
91+
23 | trio.sleep(x) # TRIO115
92+
| ^^^^^^^^^^^^^ TRIO115
93+
24 | trio.sleep(y) # OK
9394
|
9495
= help: Replace with `trio.lowlevel.checkpoint()`
9596

9697
Safe fix
97-
24 24 | trio.run(trio.sleep(0)) # TRIO115
98+
20 20 | trio.sleep(bar)
99+
21 21 |
100+
22 22 | x, y = 0, 2000
101+
23 |- trio.sleep(x) # TRIO115
102+
23 |+ trio.lowlevel.checkpoint() # TRIO115
103+
24 24 | trio.sleep(y) # OK
98104
25 25 |
99-
26 26 |
100-
27 |-from trio import Event, sleep
101-
27 |+from trio import Event, sleep, lowlevel
102-
28 28 |
103-
29 29 |
104-
30 30 | def func():
105-
31 |- sleep(0) # TRIO115
106-
31 |+ lowlevel.checkpoint() # TRIO115
107-
32 32 |
108-
33 33 |
109-
34 34 | async def func():
110-
111-
TRIO115.py:35:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
112-
|
113-
34 | async def func():
114-
35 | await sleep(seconds=0) # TRIO115
115-
| ^^^^^^^^^^^^^^^^ TRIO115
105+
26 26 | (a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
106+
107+
TRIO115.py:27:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
108+
|
109+
26 | (a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
110+
27 | trio.sleep(c) # TRIO115
111+
| ^^^^^^^^^^^^^ TRIO115
112+
28 | trio.sleep(d) # OK
113+
29 | trio.sleep(e) # TRIO115
116114
|
117115
= help: Replace with `trio.lowlevel.checkpoint()`
118116

119117
Safe fix
120-
24 24 | trio.run(trio.sleep(0)) # TRIO115
118+
24 24 | trio.sleep(y) # OK
121119
25 25 |
122-
26 26 |
123-
27 |-from trio import Event, sleep
124-
27 |+from trio import Event, sleep, lowlevel
125-
28 28 |
126-
29 29 |
127-
30 30 | def func():
120+
26 26 | (a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
121+
27 |- trio.sleep(c) # TRIO115
122+
27 |+ trio.lowlevel.checkpoint() # TRIO115
123+
28 28 | trio.sleep(d) # OK
124+
29 29 | trio.sleep(e) # TRIO115
125+
30 30 |
126+
127+
TRIO115.py:29:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
128+
|
129+
27 | trio.sleep(c) # TRIO115
130+
28 | trio.sleep(d) # OK
131+
29 | trio.sleep(e) # TRIO115
132+
| ^^^^^^^^^^^^^ TRIO115
133+
30 |
134+
31 | m_x, m_y = 0
135+
|
136+
= help: Replace with `trio.lowlevel.checkpoint()`
137+
138+
Safe fix
139+
26 26 | (a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
140+
27 27 | trio.sleep(c) # TRIO115
141+
28 28 | trio.sleep(d) # OK
142+
29 |- trio.sleep(e) # TRIO115
143+
29 |+ trio.lowlevel.checkpoint() # TRIO115
144+
30 30 |
145+
31 31 | m_x, m_y = 0
146+
32 32 | trio.sleep(m_y) # TRIO115
147+
148+
TRIO115.py:32:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
149+
|
150+
31 | m_x, m_y = 0
151+
32 | trio.sleep(m_y) # TRIO115
152+
| ^^^^^^^^^^^^^^^ TRIO115
153+
33 | trio.sleep(m_x) # TRIO115
154+
|
155+
= help: Replace with `trio.lowlevel.checkpoint()`
156+
157+
Safe fix
158+
29 29 | trio.sleep(e) # TRIO115
159+
30 30 |
160+
31 31 | m_x, m_y = 0
161+
32 |- trio.sleep(m_y) # TRIO115
162+
32 |+ trio.lowlevel.checkpoint() # TRIO115
163+
33 33 | trio.sleep(m_x) # TRIO115
164+
34 34 |
165+
35 35 | m_a = m_b = 0
166+
167+
TRIO115.py:33:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
168+
|
169+
31 | m_x, m_y = 0
170+
32 | trio.sleep(m_y) # TRIO115
171+
33 | trio.sleep(m_x) # TRIO115
172+
| ^^^^^^^^^^^^^^^ TRIO115
173+
34 |
174+
35 | m_a = m_b = 0
175+
|
176+
= help: Replace with `trio.lowlevel.checkpoint()`
177+
178+
Safe fix
179+
30 30 |
180+
31 31 | m_x, m_y = 0
181+
32 32 | trio.sleep(m_y) # TRIO115
182+
33 |- trio.sleep(m_x) # TRIO115
183+
33 |+ trio.lowlevel.checkpoint() # TRIO115
184+
34 34 |
185+
35 35 | m_a = m_b = 0
186+
36 36 | trio.sleep(m_a) # TRIO115
187+
188+
TRIO115.py:36:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
189+
|
190+
35 | m_a = m_b = 0
191+
36 | trio.sleep(m_a) # TRIO115
192+
| ^^^^^^^^^^^^^^^ TRIO115
193+
37 | trio.sleep(m_b) # TRIO115
194+
|
195+
= help: Replace with `trio.lowlevel.checkpoint()`
196+
197+
Safe fix
198+
33 33 | trio.sleep(m_x) # TRIO115
199+
34 34 |
200+
35 35 | m_a = m_b = 0
201+
36 |- trio.sleep(m_a) # TRIO115
202+
36 |+ trio.lowlevel.checkpoint() # TRIO115
203+
37 37 | trio.sleep(m_b) # TRIO115
204+
38 38 |
205+
39 39 | m_c = (m_d, m_e) = (0, 0)
206+
207+
TRIO115.py:37:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
208+
|
209+
35 | m_a = m_b = 0
210+
36 | trio.sleep(m_a) # TRIO115
211+
37 | trio.sleep(m_b) # TRIO115
212+
| ^^^^^^^^^^^^^^^ TRIO115
213+
38 |
214+
39 | m_c = (m_d, m_e) = (0, 0)
215+
|
216+
= help: Replace with `trio.lowlevel.checkpoint()`
217+
218+
Safe fix
219+
34 34 |
220+
35 35 | m_a = m_b = 0
221+
36 36 | trio.sleep(m_a) # TRIO115
222+
37 |- trio.sleep(m_b) # TRIO115
223+
37 |+ trio.lowlevel.checkpoint() # TRIO115
224+
38 38 |
225+
39 39 | m_c = (m_d, m_e) = (0, 0)
226+
40 40 | trio.sleep(m_c) # OK
227+
228+
TRIO115.py:41:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
229+
|
230+
39 | m_c = (m_d, m_e) = (0, 0)
231+
40 | trio.sleep(m_c) # OK
232+
41 | trio.sleep(m_d) # TRIO115
233+
| ^^^^^^^^^^^^^^^ TRIO115
234+
42 | trio.sleep(m_e) # TRIO115
235+
|
236+
= help: Replace with `trio.lowlevel.checkpoint()`
237+
238+
Safe fix
239+
38 38 |
240+
39 39 | m_c = (m_d, m_e) = (0, 0)
241+
40 40 | trio.sleep(m_c) # OK
242+
41 |- trio.sleep(m_d) # TRIO115
243+
41 |+ trio.lowlevel.checkpoint() # TRIO115
244+
42 42 | trio.sleep(m_e) # TRIO115
245+
43 43 |
246+
44 44 |
247+
248+
TRIO115.py:42:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
249+
|
250+
40 | trio.sleep(m_c) # OK
251+
41 | trio.sleep(m_d) # TRIO115
252+
42 | trio.sleep(m_e) # TRIO115
253+
| ^^^^^^^^^^^^^^^ TRIO115
254+
|
255+
= help: Replace with `trio.lowlevel.checkpoint()`
256+
257+
Safe fix
258+
39 39 | m_c = (m_d, m_e) = (0, 0)
259+
40 40 | trio.sleep(m_c) # OK
260+
41 41 | trio.sleep(m_d) # TRIO115
261+
42 |- trio.sleep(m_e) # TRIO115
262+
42 |+ trio.lowlevel.checkpoint() # TRIO115
263+
43 43 |
264+
44 44 |
265+
45 45 | def func():
266+
267+
TRIO115.py:53:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
268+
|
269+
52 | def func():
270+
53 | sleep(0) # TRIO115
271+
| ^^^^^^^^ TRIO115
272+
|
273+
= help: Replace with `trio.lowlevel.checkpoint()`
274+
275+
Safe fix
276+
46 46 | trio.run(trio.sleep(0)) # TRIO115
277+
47 47 |
278+
48 48 |
279+
49 |-from trio import Event, sleep
280+
49 |+from trio import Event, sleep, lowlevel
281+
50 50 |
282+
51 51 |
283+
52 52 | def func():
284+
53 |- sleep(0) # TRIO115
285+
53 |+ lowlevel.checkpoint() # TRIO115
286+
54 54 |
287+
55 55 |
288+
56 56 | async def func():
289+
290+
TRIO115.py:57:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
291+
|
292+
56 | async def func():
293+
57 | await sleep(seconds=0) # TRIO115
294+
| ^^^^^^^^^^^^^^^^ TRIO115
295+
|
296+
= help: Replace with `trio.lowlevel.checkpoint()`
297+
298+
Safe fix
299+
46 46 | trio.run(trio.sleep(0)) # TRIO115
300+
47 47 |
301+
48 48 |
302+
49 |-from trio import Event, sleep
303+
49 |+from trio import Event, sleep, lowlevel
304+
50 50 |
305+
51 51 |
306+
52 52 | def func():
128307
--------------------------------------------------------------------------------
129-
32 32 |
130-
33 33 |
131-
34 34 | async def func():
132-
35 |- await sleep(seconds=0) # TRIO115
133-
35 |+ await lowlevel.checkpoint() # TRIO115
308+
54 54 |
309+
55 55 |
310+
56 56 | async def func():
311+
57 |- await sleep(seconds=0) # TRIO115
312+
57 |+ await lowlevel.checkpoint() # TRIO115
134313

135314

crates/ruff_linter/src/rules/perflint/rules/unnecessary_list_cast.rs

+9-26
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
22
use ruff_macros::{derive_message_formats, violation};
3-
use ruff_python_ast::Stmt;
4-
use ruff_python_ast::{self as ast, Arguments, Expr};
3+
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
4+
use ruff_python_semantic::analyze::typing::find_assigned_value;
55
use ruff_text_size::TextRange;
66

77
use crate::checkers::ast::Checker;
@@ -110,30 +110,13 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr, body: &[
110110
if body.iter().any(|stmt| match_append(stmt, id)) {
111111
return;
112112
}
113-
let scope = checker.semantic().current_scope();
114-
if let Some(binding_id) = scope.get(id) {
115-
let binding = checker.semantic().binding(binding_id);
116-
if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() {
117-
if let Some(parent_id) = binding.source {
118-
let parent = checker.semantic().statement(parent_id);
119-
if let Stmt::Assign(ast::StmtAssign { value, .. })
120-
| Stmt::AnnAssign(ast::StmtAnnAssign {
121-
value: Some(value), ..
122-
})
123-
| Stmt::AugAssign(ast::StmtAugAssign { value, .. }) = parent
124-
{
125-
if matches!(
126-
value.as_ref(),
127-
Expr::Tuple(_) | Expr::List(_) | Expr::Set(_)
128-
) {
129-
let mut diagnostic =
130-
Diagnostic::new(UnnecessaryListCast, *list_range);
131-
diagnostic.set_fix(remove_cast(*list_range, *iterable_range));
132-
checker.diagnostics.push(diagnostic);
133-
}
134-
}
135-
}
136-
}
113+
let Some(value) = find_assigned_value(id, checker.semantic()) else {
114+
return;
115+
};
116+
if matches!(value, Expr::Tuple(_) | Expr::List(_) | Expr::Set(_)) {
117+
let mut diagnostic = Diagnostic::new(UnnecessaryListCast, *list_range);
118+
diagnostic.set_fix(remove_cast(*list_range, *iterable_range));
119+
checker.diagnostics.push(diagnostic);
137120
}
138121
}
139122
_ => {}

0 commit comments

Comments
 (0)