Skip to content

Commit a275851

Browse files
Fix false-positive in submodule resolution (#6435)
Closes #6433.
1 parent 1b9fed8 commit a275851

File tree

6 files changed

+67
-12
lines changed

6 files changed

+67
-12
lines changed

crates/ruff/resources/test/fixtures/pyflakes/F401_0.py

+7
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,10 @@ def b(self) -> None:
9292
case 0,:
9393
import x
9494
import y
95+
96+
97+
# Test: access a sub-importation via an alias.
98+
import foo.bar as bop
99+
import foo.bar.baz
100+
101+
print(bop.baz.read_csv("test.csv"))

crates/ruff/resources/test/fixtures/pyflakes/F823.py

+10
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,13 @@ def main():
7070

7171
def requests_mock(requests_mock: rm.Mocker):
7272
print(rm.ANY)
73+
74+
75+
import sklearn.base
76+
import mlflow.sklearn
77+
78+
79+
def f():
80+
import sklearn
81+
82+
mlflow

crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_0.py.snap

+24
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ F401_0.py:93:16: F401 [*] `x` imported but unused
151151
92 92 | case 0,:
152152
93 |- import x
153153
94 93 | import y
154+
95 94 |
155+
96 95 |
154156

155157
F401_0.py:94:16: F401 [*] `y` imported but unused
156158
|
@@ -166,5 +168,27 @@ F401_0.py:94:16: F401 [*] `y` imported but unused
166168
92 92 | case 0,:
167169
93 93 | import x
168170
94 |- import y
171+
95 94 |
172+
96 95 |
173+
97 96 | # Test: access a sub-importation via an alias.
174+
175+
F401_0.py:99:8: F401 [*] `foo.bar.baz` imported but unused
176+
|
177+
97 | # Test: access a sub-importation via an alias.
178+
98 | import foo.bar as bop
179+
99 | import foo.bar.baz
180+
| ^^^^^^^^^^^ F401
181+
100 |
182+
101 | print(bop.baz.read_csv("test.csv"))
183+
|
184+
= help: Remove unused import: `foo.bar.baz`
185+
186+
Fix
187+
96 96 |
188+
97 97 | # Test: access a sub-importation via an alias.
189+
98 98 | import foo.bar as bop
190+
99 |-import foo.bar.baz
191+
100 99 |
192+
101 100 | print(bop.baz.read_csv("test.csv"))
169193

170194

crates/ruff/src/rules/pyupgrade/rules/use_pep695_type_alias.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,18 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
130130
fn visit_expr(&mut self, expr: &'a Expr) {
131131
match expr {
132132
Expr::Name(name) if name.ctx.is_load() => {
133-
let Some(Stmt::Assign(StmtAssign { value, .. })) =
134-
self.semantic.lookup_symbol(name.id.as_str())
135-
.and_then(|binding_id| {
136-
self.semantic
137-
.binding(binding_id)
138-
.source
139-
.map(|node_id| self.semantic.statement(node_id))
140-
}) else {
141-
return;
142-
};
133+
let Some(Stmt::Assign(StmtAssign { value, .. })) = self
134+
.semantic
135+
.lookup_symbol(name.id.as_str())
136+
.and_then(|binding_id| {
137+
self.semantic
138+
.binding(binding_id)
139+
.source
140+
.map(|node_id| self.semantic.statement(node_id))
141+
})
142+
else {
143+
return;
144+
};
143145

144146
match value.as_ref() {
145147
Expr::Subscript(ExprSubscript {

crates/ruff_python_semantic/src/binding.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ impl<'a> Imported<'a> for FromImport<'a> {
585585
}
586586

587587
/// A wrapper around an import [`BindingKind`] that can be any of the three types of imports.
588-
#[derive(Debug, Clone)]
588+
#[derive(Debug, Clone, is_macro::Is)]
589589
pub enum AnyImport<'a> {
590590
Import(&'a Import<'a>),
591591
SubmoduleImport(&'a SubmoduleImport<'a>),

crates/ruff_python_semantic/src/model.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -590,14 +590,26 @@ impl<'a> SemanticModel<'a> {
590590
// print(pa.csv.read_csv("test.csv"))
591591
// ```
592592
let import = self.bindings[binding_id].as_any_import()?;
593+
if !import.is_import() {
594+
return None;
595+
}
596+
597+
// Grab, e.g., `pyarrow` from `import pyarrow as pa`.
593598
let call_path = import.call_path();
594599
let segment = call_path.last()?;
595600
if *segment == symbol {
596601
return None;
597602
}
598603

604+
// Locate the submodule import (e.g., `pyarrow.csv`) that `pa` aliases.
599605
let binding_id = self.scopes[scope_id].get(segment)?;
600-
if !self.bindings[binding_id].kind.is_submodule_import() {
606+
let submodule = &self.bindings[binding_id].as_any_import()?;
607+
if !submodule.is_submodule_import() {
608+
return None;
609+
}
610+
611+
// Ensure that the submodule import and the aliased import are from the same module.
612+
if import.module_name() != submodule.module_name() {
601613
return None;
602614
}
603615

0 commit comments

Comments
 (0)