mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 12:29:48 +00:00
Mark aliased submodule imports as used (#374)
This commit is contained in:
parent
f060248656
commit
aafe7c0c39
3 changed files with 96 additions and 40 deletions
6
resources/test/fixtures/F401.py
vendored
6
resources/test/fixtures/F401.py
vendored
|
@ -65,3 +65,9 @@ b = Union["Nut", None]
|
||||||
c = cast("Vegetable", b)
|
c = cast("Vegetable", b)
|
||||||
|
|
||||||
Field = lambda default=MISSING: field(default=default)
|
Field = lambda default=MISSING: field(default=default)
|
||||||
|
|
||||||
|
|
||||||
|
import pyarrow as pa
|
||||||
|
import pyarrow.csv
|
||||||
|
|
||||||
|
print(pa.csv.read_csv("test.csv"))
|
||||||
|
|
|
@ -74,9 +74,9 @@ pub enum BindingKind {
|
||||||
Export(Vec<String>),
|
Export(Vec<String>),
|
||||||
FutureImportation,
|
FutureImportation,
|
||||||
StarImportation,
|
StarImportation,
|
||||||
Importation(String, BindingContext),
|
Importation(String, String, BindingContext),
|
||||||
FromImportation(String, BindingContext),
|
FromImportation(String, String, BindingContext),
|
||||||
SubmoduleImportation(String, BindingContext),
|
SubmoduleImportation(String, String, BindingContext),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
124
src/check_ast.rs
124
src/check_ast.rs
|
@ -364,13 +364,16 @@ where
|
||||||
|
|
||||||
for alias in names {
|
for alias in names {
|
||||||
if alias.node.name.contains('.') && alias.node.asname.is_none() {
|
if alias.node.name.contains('.') && alias.node.asname.is_none() {
|
||||||
// TODO(charlie): Multiple submodule imports with the same parent module
|
// Given `import foo.bar`, `name` would be "foo", and `full_name` would be
|
||||||
// will be merged into a single binding.
|
// "foo.bar".
|
||||||
|
let name = alias.node.name.split('.').next().unwrap();
|
||||||
|
let full_name = &alias.node.name;
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
alias.node.name.split('.').next().unwrap().to_string(),
|
name.to_string(),
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::SubmoduleImportation(
|
kind: BindingKind::SubmoduleImportation(
|
||||||
alias.node.name.to_string(),
|
name.to_string(),
|
||||||
|
full_name.to_string(),
|
||||||
self.binding_context(),
|
self.binding_context(),
|
||||||
),
|
),
|
||||||
used: None,
|
used: None,
|
||||||
|
@ -382,19 +385,17 @@ where
|
||||||
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given `import foo`, `name` and `full_name` would both be `foo`.
|
||||||
|
// Given `import foo as bar`, `name` would be `bar` and `full_name` would
|
||||||
|
// be `foo`.
|
||||||
|
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||||
|
let full_name = &alias.node.name;
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
alias
|
name.to_string(),
|
||||||
.node
|
|
||||||
.asname
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| alias.node.name.clone()),
|
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::Importation(
|
kind: BindingKind::Importation(
|
||||||
alias
|
name.to_string(),
|
||||||
.node
|
full_name.to_string(),
|
||||||
.asname
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| alias.node.name.clone()),
|
|
||||||
self.binding_context(),
|
self.binding_context(),
|
||||||
),
|
),
|
||||||
used: None,
|
used: None,
|
||||||
|
@ -423,14 +424,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
for alias in names {
|
for alias in names {
|
||||||
let name = alias
|
|
||||||
.node
|
|
||||||
.asname
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| alias.node.name.clone());
|
|
||||||
if let Some("__future__") = module.as_deref() {
|
if let Some("__future__") = module.as_deref() {
|
||||||
|
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
name,
|
name.to_string(),
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::FutureImportation,
|
kind: BindingKind::FutureImportation,
|
||||||
used: Some((
|
used: Some((
|
||||||
|
@ -509,18 +506,26 @@ where
|
||||||
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let binding = Binding {
|
// Given `from foo import bar`, `name` would be "bar" and `full_name` would
|
||||||
kind: BindingKind::FromImportation(
|
// be "foo.bar". Given `from foo import bar as baz`, `name` would be "baz"
|
||||||
match module {
|
// and `full_name` would be "foo.bar".
|
||||||
None => name.clone(),
|
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||||
Some(parent) => format!("{}.{}", parent, name),
|
let full_name = match module {
|
||||||
},
|
None => alias.node.name.to_string(),
|
||||||
self.binding_context(),
|
Some(parent) => format!("{}.{}", parent, alias.node.name),
|
||||||
),
|
|
||||||
used: None,
|
|
||||||
range: Range::from_located(stmt),
|
|
||||||
};
|
};
|
||||||
self.add_binding(name, binding)
|
self.add_binding(
|
||||||
|
name.to_string(),
|
||||||
|
Binding {
|
||||||
|
kind: BindingKind::FromImportation(
|
||||||
|
name.to_string(),
|
||||||
|
full_name,
|
||||||
|
self.binding_context(),
|
||||||
|
),
|
||||||
|
used: None,
|
||||||
|
range: Range::from_located(stmt),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1255,6 +1260,47 @@ impl CheckLocator for Checker<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> bool {
|
||||||
|
let alias = if let Some(binding) = scope.values.get_mut(id) {
|
||||||
|
// Mark the binding as used.
|
||||||
|
binding.used = Some((scope_id, Range::from_located(expr)));
|
||||||
|
|
||||||
|
// If the name of the sub-importation is the same as an alias of another importation and the
|
||||||
|
// alias is used, that sub-importation should be marked as used too.
|
||||||
|
//
|
||||||
|
// This handles code like:
|
||||||
|
// import pyarrow as pa
|
||||||
|
// import pyarrow.csv
|
||||||
|
// print(pa.csv.read_csv("test.csv"))
|
||||||
|
if let BindingKind::Importation(name, full_name, _)
|
||||||
|
| BindingKind::FromImportation(name, full_name, _)
|
||||||
|
| BindingKind::SubmoduleImportation(name, full_name, _) = &binding.kind
|
||||||
|
{
|
||||||
|
let has_alias = full_name
|
||||||
|
.split('.')
|
||||||
|
.last()
|
||||||
|
.map(|segment| segment != name)
|
||||||
|
.unwrap_or_default();
|
||||||
|
if has_alias {
|
||||||
|
// Clone the alias. (We'll mutate it below.)
|
||||||
|
full_name.to_string()
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mark the sub-importation as used.
|
||||||
|
if let Some(binding) = scope.values.get_mut(&alias) {
|
||||||
|
binding.used = Some((scope_id, Range::from_located(expr)));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Checker<'a> {
|
impl<'a> Checker<'a> {
|
||||||
pub fn add_check(&mut self, check: Check) {
|
pub fn add_check(&mut self, check: Check) {
|
||||||
self.checks.push(check);
|
self.checks.push(check);
|
||||||
|
@ -1338,7 +1384,11 @@ impl<'a> Checker<'a> {
|
||||||
&& matches!(binding.kind, BindingKind::LoopVar)
|
&& matches!(binding.kind, BindingKind::LoopVar)
|
||||||
&& matches!(
|
&& matches!(
|
||||||
existing.kind,
|
existing.kind,
|
||||||
BindingKind::Importation(_, _) | BindingKind::FromImportation(_, _)
|
BindingKind::Importation(_, _, _)
|
||||||
|
| BindingKind::FromImportation(_, _, _)
|
||||||
|
| BindingKind::SubmoduleImportation(_, _, _)
|
||||||
|
| BindingKind::StarImportation
|
||||||
|
| BindingKind::FutureImportation
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
self.checks.push(Check::new(
|
self.checks.push(Check::new(
|
||||||
|
@ -1377,8 +1427,8 @@ impl<'a> Checker<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(binding) = scope.values.get_mut(id) {
|
|
||||||
binding.used = Some((scope_id, Range::from_located(expr)));
|
if try_mark_used(scope, scope_id, id, expr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1713,7 +1763,7 @@ impl<'a> Checker<'a> {
|
||||||
|
|
||||||
if !used {
|
if !used {
|
||||||
match &binding.kind {
|
match &binding.kind {
|
||||||
BindingKind::FromImportation(full_name, context) => {
|
BindingKind::FromImportation(_, full_name, context) => {
|
||||||
let full_names = unused
|
let full_names = unused
|
||||||
.entry((
|
.entry((
|
||||||
ImportKind::ImportFrom,
|
ImportKind::ImportFrom,
|
||||||
|
@ -1723,8 +1773,8 @@ impl<'a> Checker<'a> {
|
||||||
.or_default();
|
.or_default();
|
||||||
full_names.push(full_name);
|
full_names.push(full_name);
|
||||||
}
|
}
|
||||||
BindingKind::Importation(full_name, context)
|
BindingKind::Importation(_, full_name, context)
|
||||||
| BindingKind::SubmoduleImportation(full_name, context) => {
|
| BindingKind::SubmoduleImportation(_, full_name, context) => {
|
||||||
let full_names = unused
|
let full_names = unused
|
||||||
.entry((
|
.entry((
|
||||||
ImportKind::Import,
|
ImportKind::Import,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue