Allow `async with` in `redefined-loop-name` (#5125)

## Summary

Closes #5124.
This commit is contained in:
Charlie Marsh 2023-06-15 15:00:19 -04:00 committed by GitHub
parent c811213302
commit 107a295af4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 251 additions and 223 deletions

View File

@ -31,6 +31,21 @@ with None as i:
with None as j: # ok with None as j: # ok
pass pass
# Async with -> with, variable reused
async with None as i:
with None as i: # error
pass
# Async with -> with, different variable
async with None as i:
with None as j: # ok
pass
# Async for -> for, variable reused
async for i in []:
for i in []: # error
pass
# For -> for -> for, doubly nested variable reuse # For -> for -> for, doubly nested variable reuse
for i in []: for i in []:
for j in []: for j in []:

View File

@ -1483,7 +1483,7 @@ where
); );
} }
if self.enabled(Rule::RedefinedLoopName) { if self.enabled(Rule::RedefinedLoopName) {
pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt)); pylint::rules::redefined_loop_name(self, stmt);
} }
} }
Stmt::While(ast::StmtWhile { body, orelse, .. }) => { Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
@ -1524,7 +1524,7 @@ where
pylint::rules::useless_else_on_loop(self, stmt, body, orelse); pylint::rules::useless_else_on_loop(self, stmt, body, orelse);
} }
if self.enabled(Rule::RedefinedLoopName) { if self.enabled(Rule::RedefinedLoopName) {
pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt)); pylint::rules::redefined_loop_name(self, stmt);
} }
if self.enabled(Rule::IterationOverSet) { if self.enabled(Rule::IterationOverSet) {
pylint::rules::iteration_over_set(self, iter); pylint::rules::iteration_over_set(self, iter);

View File

@ -7,53 +7,10 @@ use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor}; use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
use ruff_python_ast::types::Node;
use ruff_python_semantic::SemanticModel; use ruff_python_semantic::SemanticModel;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum OuterBindingKind {
For,
With,
}
impl fmt::Display for OuterBindingKind {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
OuterBindingKind::For => fmt.write_str("`for` loop"),
OuterBindingKind::With => fmt.write_str("`with` statement"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum InnerBindingKind {
For,
With,
Assignment,
}
impl fmt::Display for InnerBindingKind {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
InnerBindingKind::For => fmt.write_str("`for` loop"),
InnerBindingKind::With => fmt.write_str("`with` statement"),
InnerBindingKind::Assignment => fmt.write_str("assignment"),
}
}
}
impl PartialEq<InnerBindingKind> for OuterBindingKind {
fn eq(&self, other: &InnerBindingKind) -> bool {
matches!(
(self, other),
(OuterBindingKind::For, InnerBindingKind::For)
| (OuterBindingKind::With, InnerBindingKind::With)
)
}
}
/// ## What it does /// ## What it does
/// Checks for variables defined in `for` loops and `with` statements that /// Checks for variables defined in `for` loops and `with` statements that
/// get overwritten within the body, for example by another `for` loop or /// get overwritten within the body, for example by another `for` loop or
@ -128,6 +85,48 @@ impl Violation for RedefinedLoopName {
} }
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum OuterBindingKind {
For,
With,
}
impl fmt::Display for OuterBindingKind {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
OuterBindingKind::For => fmt.write_str("`for` loop"),
OuterBindingKind::With => fmt.write_str("`with` statement"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum InnerBindingKind {
For,
With,
Assignment,
}
impl fmt::Display for InnerBindingKind {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
InnerBindingKind::For => fmt.write_str("`for` loop"),
InnerBindingKind::With => fmt.write_str("`with` statement"),
InnerBindingKind::Assignment => fmt.write_str("assignment"),
}
}
}
impl PartialEq<InnerBindingKind> for OuterBindingKind {
fn eq(&self, other: &InnerBindingKind) -> bool {
matches!(
(self, other),
(OuterBindingKind::For, InnerBindingKind::For)
| (OuterBindingKind::With, InnerBindingKind::With)
)
}
}
struct ExprWithOuterBindingKind<'a> { struct ExprWithOuterBindingKind<'a> {
expr: &'a Expr, expr: &'a Expr,
binding_kind: OuterBindingKind, binding_kind: OuterBindingKind,
@ -255,10 +254,10 @@ fn assignment_is_cast_expr(value: &Expr, target: &Expr, semantic: &SemanticModel
semantic.match_typing_expr(func, "cast") semantic.match_typing_expr(func, "cast")
} }
fn assignment_targets_from_expr<'a, U>( fn assignment_targets_from_expr<'a>(
expr: &'a Expr<U>, expr: &'a Expr,
dummy_variable_rgx: &'a Regex, dummy_variable_rgx: &'a Regex,
) -> Box<dyn Iterator<Item = &'a Expr<U>> + 'a> { ) -> Box<dyn Iterator<Item = &'a Expr> + 'a> {
// The Box is necessary to ensure the match arms have the same return type - we can't use // The Box is necessary to ensure the match arms have the same return type - we can't use
// a cast to "impl Iterator", since at the time of writing that is only allowed for // a cast to "impl Iterator", since at the time of writing that is only allowed for
// return types and argument types. // return types and argument types.
@ -308,36 +307,35 @@ fn assignment_targets_from_expr<'a, U>(
} }
} }
fn assignment_targets_from_with_items<'a, U>( fn assignment_targets_from_with_items<'a>(
items: &'a [Withitem<U>], items: &'a [Withitem],
dummy_variable_rgx: &'a Regex, dummy_variable_rgx: &'a Regex,
) -> impl Iterator<Item = &'a Expr<U>> + 'a { ) -> impl Iterator<Item = &'a Expr> + 'a {
items items
.iter() .iter()
.filter_map(|item| { .filter_map(|item| {
item.optional_vars item.optional_vars
.as_ref() .as_ref()
.map(|expr| assignment_targets_from_expr(&**expr, dummy_variable_rgx)) .map(|expr| assignment_targets_from_expr(expr, dummy_variable_rgx))
}) })
.flatten() .flatten()
} }
fn assignment_targets_from_assign_targets<'a, U>( fn assignment_targets_from_assign_targets<'a>(
targets: &'a [Expr<U>], targets: &'a [Expr],
dummy_variable_rgx: &'a Regex, dummy_variable_rgx: &'a Regex,
) -> impl Iterator<Item = &'a Expr<U>> + 'a { ) -> impl Iterator<Item = &'a Expr> + 'a {
targets targets
.iter() .iter()
.flat_map(|target| assignment_targets_from_expr(target, dummy_variable_rgx)) .flat_map(|target| assignment_targets_from_expr(target, dummy_variable_rgx))
} }
/// PLW2901 /// PLW2901
pub(crate) fn redefined_loop_name<'a, 'b>(checker: &'a mut Checker<'b>, node: &Node<'b>) { pub(crate) fn redefined_loop_name(checker: &mut Checker, stmt: &Stmt) {
let (outer_assignment_targets, inner_assignment_targets) = match node { let (outer_assignment_targets, inner_assignment_targets) = match stmt {
Node::Stmt(stmt) => match stmt { Stmt::With(ast::StmtWith { items, body, .. })
// With. | Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
Stmt::With(ast::StmtWith { items, body, .. }) => { let outer_assignment_targets: Vec<ExprWithOuterBindingKind> =
let outer_assignment_targets: Vec<ExprWithOuterBindingKind<'a>> =
assignment_targets_from_with_items(items, &checker.settings.dummy_variable_rgx) assignment_targets_from_with_items(items, &checker.settings.dummy_variable_rgx)
.map(|expr| ExprWithOuterBindingKind { .map(|expr| ExprWithOuterBindingKind {
expr, expr,
@ -354,10 +352,9 @@ pub(crate) fn redefined_loop_name<'a, 'b>(checker: &'a mut Checker<'b>, node: &N
} }
(outer_assignment_targets, visitor.assignment_targets) (outer_assignment_targets, visitor.assignment_targets)
} }
// For and async for.
Stmt::For(ast::StmtFor { target, body, .. }) Stmt::For(ast::StmtFor { target, body, .. })
| Stmt::AsyncFor(ast::StmtAsyncFor { target, body, .. }) => { | Stmt::AsyncFor(ast::StmtAsyncFor { target, body, .. }) => {
let outer_assignment_targets: Vec<ExprWithOuterBindingKind<'a>> = let outer_assignment_targets: Vec<ExprWithOuterBindingKind> =
assignment_targets_from_expr(target, &checker.settings.dummy_variable_rgx) assignment_targets_from_expr(target, &checker.settings.dummy_variable_rgx)
.map(|expr| ExprWithOuterBindingKind { .map(|expr| ExprWithOuterBindingKind {
expr, expr,
@ -375,10 +372,8 @@ pub(crate) fn redefined_loop_name<'a, 'b>(checker: &'a mut Checker<'b>, node: &N
(outer_assignment_targets, visitor.assignment_targets) (outer_assignment_targets, visitor.assignment_targets)
} }
_ => panic!( _ => panic!(
"redefined_loop_name called on Statement that is not a With, For, or AsyncFor" "redefined_loop_name called on Statement that is not a With, For, AsyncWith, or AsyncFor"
), )
},
Node::Expr(_) => panic!("redefined_loop_name called on Node that is not a Statement"),
}; };
let mut diagnostics = Vec::new(); let mut diagnostics = Vec::new();

View File

@ -37,197 +37,215 @@ redefined_loop_name.py:21:18: PLW2901 Outer `with` statement variable `i` overwr
22 | pass 22 | pass
| |
redefined_loop_name.py:37:13: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target redefined_loop_name.py:36:18: PLW2901 Outer `with` statement variable `i` overwritten by inner `with` statement target
| |
35 | for i in []: 34 | # Async with -> with, variable reused
36 | for j in []: 35 | async with None as i:
37 | for i in []: # error 36 | with None as i: # error
| ^ PLW2901 | ^ PLW2901
38 | pass 37 | pass
| |
redefined_loop_name.py:43:13: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target redefined_loop_name.py:46:9: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
| |
41 | for i in []: 44 | # Async for -> for, variable reused
42 | for j in []: 45 | async for i in []:
43 | for i in []: # error 46 | for i in []: # error
| ^ PLW2901 | ^ PLW2901
44 | for j in []: # error 47 | pass
45 | pass
| |
redefined_loop_name.py:44:17: PLW2901 Outer `for` loop variable `j` overwritten by inner `for` loop target redefined_loop_name.py:52:13: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
| |
42 | for j in []: 50 | for i in []:
43 | for i in []: # error 51 | for j in []:
44 | for j in []: # error 52 | for i in []: # error
| ^ PLW2901 | ^ PLW2901
45 | pass 53 | pass
| |
redefined_loop_name.py:52:5: PLW2901 `for` loop variable `i` overwritten by assignment target redefined_loop_name.py:58:13: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
| |
50 | i = cast(int, i) 56 | for i in []:
51 | i = typing.cast(int, i) 57 | for j in []:
52 | i = 5 # error 58 | for i in []: # error
| ^ PLW2901 | ^ PLW2901
53 | 59 | for j in []: # error
54 | # For -> augmented assignment 60 | pass
| |
redefined_loop_name.py:56:5: PLW2901 `for` loop variable `i` overwritten by assignment target redefined_loop_name.py:59:17: PLW2901 Outer `for` loop variable `j` overwritten by inner `for` loop target
| |
54 | # For -> augmented assignment 57 | for j in []:
55 | for i in []: 58 | for i in []: # error
56 | i += 5 # error 59 | for j in []: # error
| ^ PLW2901 | ^ PLW2901
57 | 60 | pass
58 | # For -> annotated assignment
| |
redefined_loop_name.py:60:5: PLW2901 `for` loop variable `i` overwritten by assignment target redefined_loop_name.py:67:5: PLW2901 `for` loop variable `i` overwritten by assignment target
| |
58 | # For -> annotated assignment 65 | i = cast(int, i)
59 | for i in []: 66 | i = typing.cast(int, i)
60 | i: int = 5 # error 67 | i = 5 # error
| ^ PLW2901 | ^ PLW2901
61 | 68 |
62 | # For -> annotated assignment without value 69 | # For -> augmented assignment
| |
redefined_loop_name.py:68:9: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target redefined_loop_name.py:71:5: PLW2901 `for` loop variable `i` overwritten by assignment target
| |
66 | # Async for -> for, variable reused 69 | # For -> augmented assignment
67 | async for i in []: 70 | for i in []:
68 | for i in []: # error 71 | i += 5 # error
| ^ PLW2901 | ^ PLW2901
69 | pass 72 |
73 | # For -> annotated assignment
| |
redefined_loop_name.py:73:15: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target redefined_loop_name.py:75:5: PLW2901 `for` loop variable `i` overwritten by assignment target
| |
71 | # For -> async for, variable reused 73 | # For -> annotated assignment
72 | for i in []: 74 | for i in []:
73 | async for i in []: # error 75 | i: int = 5 # error
| ^ PLW2901 | ^ PLW2901
74 | pass 76 |
| 77 | # For -> annotated assignment without value
redefined_loop_name.py:78:9: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
|
76 | # For -> for, outer loop unpacks tuple
77 | for i, j in enumerate([]):
78 | for i in []: # error
| ^ PLW2901
79 | pass
| |
redefined_loop_name.py:83:9: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target redefined_loop_name.py:83:9: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
| |
81 | # For -> for, inner loop unpacks tuple 81 | # Async for -> for, variable reused
82 | for i in []: 82 | async for i in []:
83 | for i, j in enumerate([]): # error 83 | for i in []: # error
| ^ PLW2901 | ^ PLW2901
84 | pass 84 | pass
| |
redefined_loop_name.py:88:9: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target redefined_loop_name.py:88:15: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
| |
86 | # For -> for, both loops unpack tuple 86 | # For -> async for, variable reused
87 | for (i, (j, k)) in []: 87 | for i in []:
88 | for i, j in enumerate([]): # two errors 88 | async for i in []: # error
| ^ PLW2901 | ^ PLW2901
89 | pass 89 | pass
| |
redefined_loop_name.py:88:12: PLW2901 Outer `for` loop variable `j` overwritten by inner `for` loop target redefined_loop_name.py:93:9: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
| |
86 | # For -> for, both loops unpack tuple 91 | # For -> for, outer loop unpacks tuple
87 | for (i, (j, k)) in []: 92 | for i, j in enumerate([]):
88 | for i, j in enumerate([]): # two errors 93 | for i in []: # error
| ^ PLW2901 | ^ PLW2901
89 | pass 94 | pass
| |
redefined_loop_name.py:105:9: PLW2901 Outer `for` loop variable `j` overwritten by inner `for` loop target redefined_loop_name.py:98:9: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
| |
103 | # For -> for, outer loop unpacks with asterisk 96 | # For -> for, inner loop unpacks tuple
104 | for i, *j in []: 97 | for i in []:
105 | for j in []: # error 98 | for i, j in enumerate([]): # error
| ^ PLW2901 | ^ PLW2901
106 | pass 99 | pass
| |
redefined_loop_name.py:122:13: PLW2901 `for` loop variable `i` overwritten by assignment target redefined_loop_name.py:103:9: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
| |
120 | def f(): 101 | # For -> for, both loops unpack tuple
121 | for i in []: # no error 102 | for (i, (j, k)) in []:
122 | i = 2 # error 103 | for i, j in enumerate([]): # two errors
| ^ PLW2901 | ^ PLW2901
123 | 104 | pass
124 | # For -> class definition -> for -> for
| |
redefined_loop_name.py:128:17: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target redefined_loop_name.py:103:12: PLW2901 Outer `for` loop variable `j` overwritten by inner `for` loop target
| |
126 | class A: 101 | # For -> for, both loops unpack tuple
127 | for i in []: # no error 102 | for (i, (j, k)) in []:
128 | for i in []: # error 103 | for i, j in enumerate([]): # two errors
| ^ PLW2901 | ^ PLW2901
129 | pass 104 | pass
| |
redefined_loop_name.py:143:5: PLW2901 `for` loop variable `a[0]` overwritten by assignment target redefined_loop_name.py:120:9: PLW2901 Outer `for` loop variable `j` overwritten by inner `for` loop target
| |
141 | # For target with subscript -> assignment 118 | # For -> for, outer loop unpacks with asterisk
142 | for a[0] in []: 119 | for i, *j in []:
143 | a[0] = 2 # error 120 | for j in []: # error
| ^ PLW2901
121 | pass
|
redefined_loop_name.py:137:13: PLW2901 `for` loop variable `i` overwritten by assignment target
|
135 | def f():
136 | for i in []: # no error
137 | i = 2 # error
| ^ PLW2901
138 |
139 | # For -> class definition -> for -> for
|
redefined_loop_name.py:143:17: PLW2901 Outer `for` loop variable `i` overwritten by inner `for` loop target
|
141 | class A:
142 | for i in []: # no error
143 | for i in []: # error
| ^ PLW2901
144 | pass
|
redefined_loop_name.py:158:5: PLW2901 `for` loop variable `a[0]` overwritten by assignment target
|
156 | # For target with subscript -> assignment
157 | for a[0] in []:
158 | a[0] = 2 # error
| ^^^^ PLW2901 | ^^^^ PLW2901
144 | a[1] = 2 # no error 159 | a[1] = 2 # no error
| |
redefined_loop_name.py:148:5: PLW2901 `for` loop variable `a['i']` overwritten by assignment target redefined_loop_name.py:163:5: PLW2901 `for` loop variable `a['i']` overwritten by assignment target
| |
146 | # For target with subscript -> assignment 161 | # For target with subscript -> assignment
147 | for a['i'] in []: 162 | for a['i'] in []:
148 | a['i'] = 2 # error 163 | a['i'] = 2 # error
| ^^^^^^ PLW2901 | ^^^^^^ PLW2901
149 | a['j'] = 2 # no error 164 | a['j'] = 2 # no error
| |
redefined_loop_name.py:153:5: PLW2901 `for` loop variable `a.i` overwritten by assignment target redefined_loop_name.py:168:5: PLW2901 `for` loop variable `a.i` overwritten by assignment target
| |
151 | # For target with attribute -> assignment 166 | # For target with attribute -> assignment
152 | for a.i in []: 167 | for a.i in []:
153 | a.i = 2 # error 168 | a.i = 2 # error
| ^^^ PLW2901 | ^^^ PLW2901
154 | a.j = 2 # no error 169 | a.j = 2 # no error
| |
redefined_loop_name.py:158:5: PLW2901 `for` loop variable `a.i.j` overwritten by assignment target redefined_loop_name.py:173:5: PLW2901 `for` loop variable `a.i.j` overwritten by assignment target
| |
156 | # For target with double nested attribute -> assignment 171 | # For target with double nested attribute -> assignment
157 | for a.i.j in []: 172 | for a.i.j in []:
158 | a.i.j = 2 # error 173 | a.i.j = 2 # error
| ^^^^^ PLW2901 | ^^^^^ PLW2901
159 | a.j.i = 2 # no error 174 | a.j.i = 2 # no error
| |
redefined_loop_name.py:163:5: PLW2901 `for` loop variable `a.i` overwritten by assignment target redefined_loop_name.py:178:5: PLW2901 `for` loop variable `a.i` overwritten by assignment target
| |
161 | # For target with attribute -> assignment with different spacing 176 | # For target with attribute -> assignment with different spacing
162 | for a.i in []: 177 | for a.i in []:
163 | a. i = 2 # error 178 | a. i = 2 # error
| ^^^^ PLW2901 | ^^^^ PLW2901
164 | for a. i in []: 179 | for a. i in []:
165 | a.i = 2 # error 180 | a.i = 2 # error
| |
redefined_loop_name.py:165:5: PLW2901 `for` loop variable `a.i` overwritten by assignment target redefined_loop_name.py:180:5: PLW2901 `for` loop variable `a.i` overwritten by assignment target
| |
163 | a. i = 2 # error 178 | a. i = 2 # error
164 | for a. i in []: 179 | for a. i in []:
165 | a.i = 2 # error 180 | a.i = 2 # error
| ^^^ PLW2901 | ^^^ PLW2901
| |