Skip to content

Commit 6a165cc

Browse files
committed
[pylint]: call swap_with_temporary_variable from definition analysis step
1 parent 51bbda1 commit 6a165cc

3 files changed

Lines changed: 45 additions & 67 deletions

File tree

crates/ruff_linter/src/checkers/ast/analyze/definitions.rs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,33 @@ use crate::{docstrings, warn_user};
1717
/// it is expected that all [`Definition`] nodes have been visited by the time, and that this
1818
/// method will not recurse into any other nodes.
1919
pub(crate) fn definitions(checker: &mut Checker) {
20+
// Compute visibility of all definitions.
21+
let exports: Option<Vec<DunderAllName>> = {
22+
checker
23+
.semantic
24+
.global_scope()
25+
.get_all("__all__")
26+
.map(|binding_id| &checker.semantic.bindings[binding_id])
27+
.filter_map(|binding| match &binding.kind {
28+
BindingKind::Export(Export { names }) => Some(names.iter().copied()),
29+
_ => None,
30+
})
31+
.fold(None, |acc, names| {
32+
Some(acc.into_iter().flatten().chain(names).collect())
33+
})
34+
};
35+
36+
let definitions = std::mem::take(&mut checker.semantic.definitions);
37+
for ContextualizedDefinition {
38+
definition,
39+
visibility: _,
40+
} in definitions.resolve(exports.as_deref()).iter()
41+
{
42+
if checker.is_rule_enabled(Rule::SwapWithTemporaryVariable) {
43+
pylint::rules::swap_with_temporary_variable(checker, definition);
44+
}
45+
}
46+
2047
let enforce_annotations = checker.any_rule_enabled(&[
2148
Rule::AnyType,
2249
Rule::MissingReturnTypeClassMethod,
@@ -100,24 +127,8 @@ pub(crate) fn definitions(checker: &mut Checker) {
100127
return;
101128
}
102129

103-
// Compute visibility of all definitions.
104-
let exports: Option<Vec<DunderAllName>> = {
105-
checker
106-
.semantic
107-
.global_scope()
108-
.get_all("__all__")
109-
.map(|binding_id| &checker.semantic.bindings[binding_id])
110-
.filter_map(|binding| match &binding.kind {
111-
BindingKind::Export(Export { names }) => Some(names.iter().copied()),
112-
_ => None,
113-
})
114-
.fold(None, |acc, names| {
115-
Some(acc.into_iter().flatten().chain(names).collect())
116-
})
117-
};
118-
119-
let definitions = std::mem::take(&mut checker.semantic.definitions);
120130
let mut overloaded_name: Option<&str> = None;
131+
let definitions = std::mem::take(&mut checker.semantic.definitions);
121132
for ContextualizedDefinition {
122133
definition,
123134
visibility,

crates/ruff_linter/src/checkers/ast/analyze/statement.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,9 +1476,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
14761476
if checker.is_rule_enabled(Rule::NonPEP695TypeAlias) {
14771477
pyupgrade::rules::non_pep695_type_alias_type(checker, assign);
14781478
}
1479-
if checker.is_rule_enabled(Rule::SwapWithTemporaryVariable) {
1480-
pylint::rules::swap_with_temporary_variable(checker, stmt);
1481-
}
14821479
}
14831480
Stmt::AnnAssign(
14841481
assign_stmt @ ast::StmtAnnAssign {
@@ -1528,9 +1525,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
15281525
if checker.is_rule_enabled(Rule::UnsortedDunderAll) {
15291526
ruff::rules::sort_dunder_all_ann_assign(checker, assign_stmt);
15301527
}
1531-
if checker.is_rule_enabled(Rule::SwapWithTemporaryVariable) {
1532-
pylint::rules::swap_with_temporary_variable(checker, stmt);
1533-
}
15341528
if checker.source_type.is_stub() {
15351529
if let Some(value) = value {
15361530
if checker.is_rule_enabled(Rule::AssignmentDefaultInStub) {

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

Lines changed: 17 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use itertools::Itertools;
22
use ruff_diagnostics::{Applicability, Edit, Fix};
3+
use ruff_python_ast::Stmt;
34
use ruff_python_ast::name::Name;
4-
use ruff_python_ast::traversal::EnclosingSuite;
5-
use ruff_python_ast::{Stmt, traversal};
6-
use ruff_python_semantic::SemanticModel;
5+
use ruff_python_semantic::Definition;
76
use ruff_text_size::{Ranged, TextRange, TextSize};
87

98
use ruff_macros::{ViolationMetadata, derive_message_formats};
@@ -42,7 +41,7 @@ use crate::checkers::ast::Checker;
4241
/// exception case, applying the quick fix would remove comments between the
4342
/// assignment statements.
4443
#[derive(ViolationMetadata)]
45-
#[violation_metadata(preview_since = "0.14.11")]
44+
#[violation_metadata(preview_since = "0.15.1")]
4645
pub(crate) struct SwapWithTemporaryVariable<'a> {
4746
first_var: &'a Name,
4847
second_var: &'a Name,
@@ -79,13 +78,14 @@ impl Violation for SwapWithTemporaryVariable<'_> {
7978
}
8079
}
8180

82-
pub(crate) fn swap_with_temporary_variable(checker: &Checker, assignment: &Stmt) {
83-
let Some(consecutive_assignments) =
84-
match_consecutive_assignments(assignment, checker.semantic())
85-
else {
86-
return;
87-
};
88-
for (stmt_a, stmt_b, stmt_c) in consecutive_assignments.tuple_windows() {
81+
pub(crate) fn swap_with_temporary_variable(checker: &Checker, definition: &Definition) {
82+
let consecutive_assignments = match_consecutive_assignments(definition);
83+
84+
for stmt_sequence in consecutive_assignments.tuple_windows() {
85+
let (Some(stmt_a), Some(stmt_b), Some(stmt_c)) = stmt_sequence else {
86+
continue;
87+
};
88+
8989
// Detect patterns like:
9090
// temp = x
9191
// x = y
@@ -134,41 +134,14 @@ pub(crate) fn swap_with_temporary_variable(checker: &Checker, assignment: &Stmt)
134134
///
135135
/// Also see the `repeated_append` rule for a similar use case.
136136
fn match_consecutive_assignments<'a>(
137-
stmt: &'a Stmt,
138-
semantic: &'a SemanticModel,
139-
) -> Option<impl Iterator<Item = VarToVarAssignment<'a>>> {
140-
let root_assignment = VarToVarAssignment::from_stmt(stmt)?;
141-
142-
// In order to match consecutive statements, we need to go to the tree ancestor of the
143-
// given statement, find its position there, and match all 'appends' from there.
144-
let suite = if semantic.at_top_level() {
145-
// If the statement is at the top level, we should go to the parent module.
146-
// Module is available in the definitions list.
147-
EnclosingSuite::new(semantic.definitions.python_ast()?, stmt.into())?
148-
} else {
149-
// Otherwise, go to the parent, and take its body as a sequence of siblings.
150-
semantic
151-
.current_statement_parent()
152-
.and_then(|parent| traversal::suite(stmt, parent))?
137+
definition: &'a Definition,
138+
) -> impl Iterator<Item = Option<VarToVarAssignment<'a>>> {
139+
let suite = match definition {
140+
Definition::Module(module) => module.python_ast,
141+
Definition::Member(member) => member.body(),
153142
};
154143

155-
// We shouldn't repeat the same work for many 'assignments' that go in a row. Let's check
156-
// that this statement is at the beginning of such a group.
157-
if suite
158-
.previous_sibling()
159-
.is_some_and(|previous_stmt| VarToVarAssignment::from_stmt(previous_stmt).is_some())
160-
{
161-
return None;
162-
}
163-
164-
Some(
165-
std::iter::once(root_assignment).chain(
166-
suite
167-
.next_siblings()
168-
.iter()
169-
.map_while(VarToVarAssignment::from_stmt),
170-
),
171-
)
144+
suite.iter().map(VarToVarAssignment::from_stmt)
172145
}
173146

174147
#[derive(Eq, PartialEq, Debug, Clone)]

0 commit comments

Comments
 (0)