Mark aliased submodule imports as used (#374)

This commit is contained in:
Charlie Marsh 2022-10-09 17:01:14 -04:00 committed by GitHub
parent f060248656
commit aafe7c0c39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 40 deletions

View file

@ -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"))

View file

@ -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)]

View file

@ -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
// be "foo.bar". Given `from foo import bar as baz`, `name` would be "baz"
// and `full_name` would be "foo.bar".
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
let full_name = match module {
None => alias.node.name.to_string(),
Some(parent) => format!("{}.{}", parent, alias.node.name),
};
self.add_binding(
name.to_string(),
Binding {
kind: BindingKind::FromImportation( kind: BindingKind::FromImportation(
match module { name.to_string(),
None => name.clone(), full_name,
Some(parent) => format!("{}.{}", parent, name),
},
self.binding_context(), self.binding_context(),
), ),
used: None, used: None,
range: Range::from_located(stmt), range: Range::from_located(stmt),
}; },
self.add_binding(name, binding) )
} }
} }
} }
@ -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,