Skip to content

Commit fd09d37

Browse files
authored
Fix panic on access to definitions after analyzing definitions (#23588)
Summary -- This PR fixes #23587 by removing this `std::mem::take` call: https://github.com/astral-sh/ruff/blob/a62ba8c6e2bac0b899d90fd30a1b26c07aac44bb/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs#L120 This was previously moving the `Definitions` out of the `Checker` and causing this operation to panic if the definitions were accessed in a later analysis phase: https://github.com/astral-sh/ruff/blob/a62ba8c6e2bac0b899d90fd30a1b26c07aac44bb/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs#L120 which was apparently never done before `PLR1712` was added. I don't see any reason why this really needed to be a move as it only took a couple of lifetimes to handle it with borrowing, so this seems like the easiest fix. I also changed the indexing operation to a `get` call (with a `debug_assert`) since the method already returns an `Option`, but doing that alone would prevent `PLR1712` from firing in the module scope. Test Plan -- A new test based on the issue that also covers the module scope mentioned above
1 parent 81d655f commit fd09d37

5 files changed

Lines changed: 56 additions & 6 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""
2+
Test that PLR1712 fires in the module-global scope.
3+
"""
4+
5+
x, y = 1, 2
6+
temp = x # PLR1712
7+
x = y
8+
y = temp

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,15 @@ pub(crate) fn definitions(checker: &mut Checker) {
117117
})
118118
};
119119

120-
let definitions = std::mem::take(&mut checker.semantic.definitions);
121120
let mut overloaded_name: Option<&str> = None;
122121
for ContextualizedDefinition {
123122
definition,
124123
visibility,
125-
} in definitions.resolve(exports.as_deref()).iter()
124+
} in checker
125+
.semantic
126+
.definitions
127+
.resolve(exports.as_deref())
128+
.iter()
126129
{
127130
let docstring = docstrings::extraction::extract_docstring(definition);
128131

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,4 +474,18 @@ mod tests {
474474
assert_diagnostics!(diagnostics);
475475
Ok(())
476476
}
477+
478+
/// Regression test for <https://github.com/astral-sh/ruff/issues/23587>.
479+
#[test]
480+
fn conflict_with_definition_rules() -> Result<()> {
481+
let diagnostics = test_path(
482+
Path::new("pylint/swap_with_temporary_variable_1.py"),
483+
&LinterSettings::for_rules(vec![
484+
Rule::SwapWithTemporaryVariable,
485+
Rule::MissingTypeFunctionArgument,
486+
]),
487+
)?;
488+
assert_diagnostics!(diagnostics);
489+
Ok(())
490+
}
477491
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: crates/ruff_linter/src/rules/pylint/mod.rs
3+
---
4+
PLR1712 [*] Unnecessary temporary variable
5+
--> swap_with_temporary_variable_1.py:6:1
6+
|
7+
5 | x, y = 1, 2
8+
6 | / temp = x # PLR1712
9+
7 | | x = y
10+
8 | | y = temp
11+
| |________^
12+
|
13+
help: Use `x, y = y, x` instead
14+
3 | """
15+
4 |
16+
5 | x, y = 1, 2
17+
- temp = x # PLR1712
18+
- x = y
19+
- y = temp
20+
6 + x, y = y, x
21+
note: This is an unsafe fix and may change runtime behavior

crates/ruff_python_semantic/src/definition.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,11 +214,11 @@ impl<'a> Definitions<'a> {
214214
}
215215

216216
/// Resolve the visibility of each definition in the collection.
217-
pub fn resolve(self, exports: Option<&[DunderAllName]>) -> ContextualizedDefinitions<'a> {
217+
pub fn resolve(&'a self, exports: Option<&[DunderAllName]>) -> ContextualizedDefinitions<'a> {
218218
let mut definitions: IndexVec<DefinitionId, ContextualizedDefinition<'a>> =
219219
IndexVec::with_capacity(self.len());
220220

221-
for definition in self {
221+
for definition in self.iter() {
222222
// Determine the visibility of the next definition, taking into account its parent's
223223
// visibility.
224224
let visibility = {
@@ -282,7 +282,11 @@ impl<'a> Definitions<'a> {
282282

283283
/// Returns a reference to the Python AST.
284284
pub fn python_ast(&self) -> Option<&'a [Stmt]> {
285-
let module = self[DefinitionId::module()].as_module()?;
285+
let Some(definition) = self.get(DefinitionId::module()) else {
286+
debug_assert!(false, "Module definition unavailable");
287+
return None;
288+
};
289+
let module = definition.as_module()?;
286290
Some(module.python_ast)
287291
}
288292
}
@@ -306,7 +310,7 @@ impl<'a> IntoIterator for Definitions<'a> {
306310

307311
/// A [`Definition`] in a Python program with its resolved [`Visibility`].
308312
pub struct ContextualizedDefinition<'a> {
309-
pub definition: Definition<'a>,
313+
pub definition: &'a Definition<'a>,
310314
pub visibility: Visibility,
311315
}
312316

0 commit comments

Comments
 (0)