Store the call path

This commit is contained in:
Charlie Marsh 2023-07-25 22:16:48 -04:00
parent de898c52eb
commit 15273c6d95
8 changed files with 78 additions and 65 deletions

View File

@ -26,6 +26,7 @@
//! represents the lint-rule analysis phase. In the future, these steps may be separated into //! represents the lint-rule analysis phase. In the future, these steps may be separated into
//! distinct passes over the AST. //! distinct passes over the AST.
use std::iter::once;
use std::path::Path; use std::path::Path;
use itertools::Itertools; use itertools::Itertools;
@ -320,10 +321,14 @@ where
// "foo.bar". // "foo.bar".
let name = alias.name.split('.').next().unwrap(); let name = alias.name.split('.').next().unwrap();
let qualified_name = &alias.name; let qualified_name = &alias.name;
let call_path: Box<[&str]> = qualified_name.split('.').collect();
self.add_binding( self.add_binding(
name, name,
alias.identifier(), alias.identifier(),
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }), BindingKind::SubmoduleImport(SubmoduleImport {
qualified_name,
call_path,
}),
BindingFlags::EXTERNAL, BindingFlags::EXTERNAL,
); );
} else { } else {
@ -341,10 +346,14 @@ where
let name = alias.asname.as_ref().unwrap_or(&alias.name); let name = alias.asname.as_ref().unwrap_or(&alias.name);
let qualified_name = &alias.name; let qualified_name = &alias.name;
let call_path: Box<[&str]> = qualified_name.split('.').collect();
self.add_binding( self.add_binding(
name, name,
alias.identifier(), alias.identifier(),
BindingKind::Import(Import { qualified_name }), BindingKind::Import(Import {
qualified_name,
call_path,
}),
flags, flags,
); );
} }
@ -390,10 +399,16 @@ where
let name = alias.asname.as_ref().unwrap_or(&alias.name); let name = alias.asname.as_ref().unwrap_or(&alias.name);
let qualified_name = let qualified_name =
helpers::format_import_from_member(level, module, &alias.name); helpers::format_import_from_member(level, module, &alias.name);
let module = module.unwrap();
let call_path: Box<[&str]> =
module.split('.').chain(once(alias.name.as_str())).collect();
self.add_binding( self.add_binding(
name, name,
alias.identifier(), alias.identifier(),
BindingKind::FromImport(FromImport { qualified_name }), BindingKind::FromImport(FromImport {
qualified_name,
call_path,
}),
flags, flags,
); );
} }

View File

@ -50,7 +50,7 @@ pub(crate) fn unaliased_collections_abc_set_import(
checker: &Checker, checker: &Checker,
binding: &Binding, binding: &Binding,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
let BindingKind::FromImport(FromImport { qualified_name }) = &binding.kind else { let BindingKind::FromImport(FromImport { qualified_name, .. }) = &binding.kind else {
return None; return None;
}; };
if qualified_name.as_str() != "collections.abc.Set" { if qualified_name.as_str() != "collections.abc.Set" {

View File

@ -51,7 +51,7 @@ impl Violation for RuntimeImportInTypeCheckingBlock {
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
let RuntimeImportInTypeCheckingBlock { qualified_name } = self; let RuntimeImportInTypeCheckingBlock { qualified_name, .. } = self;
format!( format!(
"Move import `{qualified_name}` out of type-checking block. Import is used for more than type hinting." "Move import `{qualified_name}` out of type-checking block. Import is used for more than type hinting."
) )

View File

@ -39,9 +39,11 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti
| BindingKind::LoopVar | BindingKind::LoopVar
| BindingKind::Global | BindingKind::Global
| BindingKind::Nonlocal(_) => Resolution::RelevantLocal, | BindingKind::Nonlocal(_) => Resolution::RelevantLocal,
BindingKind::Import(Import { BindingKind::Import(Import { qualified_name, .. })
qualified_name: module, if qualified_name == "pandas" =>
}) if module == "pandas" => Resolution::PandasModule, {
Resolution::PandasModule
}
_ => Resolution::IrrelevantBinding, _ => Resolution::IrrelevantBinding,
} }
}) })

View File

@ -68,7 +68,8 @@ pub(crate) fn inplace_argument(
matches!( matches!(
binding.kind, binding.kind,
BindingKind::Import(Import { BindingKind::Import(Import {
qualified_name: "pandas" qualified_name: "pandas",
..
}) })
) )
}) })

View File

@ -5,6 +5,7 @@ use ruff_python_ast::Ranged;
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use ruff_index::{newtype_index, IndexSlice, IndexVec}; use ruff_index::{newtype_index, IndexSlice, IndexVec};
use ruff_python_ast::source_code::Locator;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use crate::context::ExecutionContext; use crate::context::ExecutionContext;
@ -117,38 +118,38 @@ impl<'a> Binding<'a> {
// import foo.baz // import foo.baz
// ``` // ```
BindingKind::Import(Import { BindingKind::Import(Import {
qualified_name: redefinition, call_path: redefinition,
}) => { }) => {
if let BindingKind::SubmoduleImport(SubmoduleImport { if let BindingKind::SubmoduleImport(SubmoduleImport {
qualified_name: definition, call_path: definition,
}) = &existing.kind }) = &existing.kind
{ {
return redefinition == definition; return redefinition == definition;
} }
} }
BindingKind::FromImport(FromImport { BindingKind::FromImport(FromImport {
qualified_name: redefinition, call_path: redefinition,
}) => { }) => {
if let BindingKind::SubmoduleImport(SubmoduleImport { if let BindingKind::SubmoduleImport(SubmoduleImport {
qualified_name: definition, call_path: definition,
}) = &existing.kind }) = &existing.kind
{ {
return redefinition == definition; return redefinition == definition;
} }
} }
BindingKind::SubmoduleImport(SubmoduleImport { BindingKind::SubmoduleImport(SubmoduleImport {
qualified_name: redefinition, call_path: redefinition,
}) => match &existing.kind { }) => match &existing.kind {
BindingKind::Import(Import { BindingKind::Import(Import {
qualified_name: definition, call_path: definition,
}) })
| BindingKind::SubmoduleImport(SubmoduleImport { | BindingKind::SubmoduleImport(SubmoduleImport {
qualified_name: definition, call_path: definition,
}) => { }) => {
return redefinition == definition; return redefinition == definition;
} }
BindingKind::FromImport(FromImport { BindingKind::FromImport(FromImport {
qualified_name: definition, call_path: definition,
}) => { }) => {
return redefinition == definition; return redefinition == definition;
} }
@ -178,9 +179,9 @@ impl<'a> Binding<'a> {
/// Returns the fully-qualified symbol name, if this symbol was imported from another module. /// Returns the fully-qualified symbol name, if this symbol was imported from another module.
pub fn qualified_name(&self) -> Option<&str> { pub fn qualified_name(&self) -> Option<&str> {
match &self.kind { match &self.kind {
BindingKind::Import(Import { qualified_name }) => Some(qualified_name), BindingKind::Import(Import { qualified_name, .. }) => Some(qualified_name),
BindingKind::FromImport(FromImport { qualified_name }) => Some(qualified_name), BindingKind::FromImport(FromImport { qualified_name, .. }) => Some(qualified_name),
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }) => { BindingKind::SubmoduleImport(SubmoduleImport { qualified_name, .. }) => {
Some(qualified_name) Some(qualified_name)
} }
_ => None, _ => None,
@ -191,11 +192,11 @@ impl<'a> Binding<'a> {
/// symbol was imported from another module. /// symbol was imported from another module.
pub fn module_name(&self) -> Option<&str> { pub fn module_name(&self) -> Option<&str> {
match &self.kind { match &self.kind {
BindingKind::Import(Import { qualified_name }) BindingKind::Import(Import { qualified_name, .. })
| BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }) => { | BindingKind::SubmoduleImport(SubmoduleImport { qualified_name, .. }) => {
Some(qualified_name.split('.').next().unwrap_or(qualified_name)) Some(qualified_name.split('.').next().unwrap_or(qualified_name))
} }
BindingKind::FromImport(FromImport { qualified_name }) => Some( BindingKind::FromImport(FromImport { qualified_name, .. }) => Some(
qualified_name qualified_name
.rsplit_once('.') .rsplit_once('.')
.map_or(qualified_name, |(module, _)| module), .map_or(qualified_name, |(module, _)| module),
@ -357,17 +358,19 @@ pub struct Import<'a> {
/// Ex) Given `import foo`, `qualified_name` would be "foo". /// Ex) Given `import foo`, `qualified_name` would be "foo".
/// Ex) Given `import foo as bar`, `qualified_name` would be "foo". /// Ex) Given `import foo as bar`, `qualified_name` would be "foo".
pub qualified_name: &'a str, pub qualified_name: &'a str,
pub call_path: Box<[&'a str]>,
} }
/// A binding for a member imported from a module, keyed on the name to which the member is bound. /// A binding for a member imported from a module, keyed on the name to which the member is bound.
/// Ex) `from foo import bar` would be keyed on "bar". /// Ex) `from foo import bar` would be keyed on "bar".
/// Ex) `from foo import bar as baz` would be keyed on "baz". /// Ex) `from foo import bar as baz` would be keyed on "baz".
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FromImport { pub struct FromImport<'a> {
/// The full name of the member being imported. /// The full name of the member being imported.
/// Ex) Given `from foo import bar`, `qualified_name` would be "foo.bar". /// Ex) Given `from foo import bar`, `qualified_name` would be "foo.bar".
/// Ex) Given `from foo import bar as baz`, `qualified_name` would be "foo.bar". /// Ex) Given `from foo import bar as baz`, `qualified_name` would be "foo.bar".
pub qualified_name: String, pub qualified_name: String,
pub call_path: Box<[&'a str]>,
} }
/// A binding for a submodule imported from a module, keyed on the name of the parent module. /// A binding for a submodule imported from a module, keyed on the name of the parent module.
@ -377,6 +380,7 @@ pub struct SubmoduleImport<'a> {
/// The full name of the submodule being imported. /// The full name of the submodule being imported.
/// Ex) Given `import foo.bar`, `qualified_name` would be "foo.bar". /// Ex) Given `import foo.bar`, `qualified_name` would be "foo.bar".
pub qualified_name: &'a str, pub qualified_name: &'a str,
pub call_path: Box<[&'a str]>,
} }
#[derive(Debug, Clone, is_macro::Is)] #[derive(Debug, Clone, is_macro::Is)]
@ -485,7 +489,7 @@ pub enum BindingKind<'a> {
/// ```python /// ```python
/// from foo import bar /// from foo import bar
/// ``` /// ```
FromImport(FromImport), FromImport(FromImport<'a>),
/// A binding for a submodule imported from a module, like `bar` in: /// A binding for a submodule imported from a module, like `bar` in:
/// ```python /// ```python

View File

@ -1,15 +1,14 @@
use std::path::Path; use std::path::Path;
use bitflags::bitflags; use bitflags::bitflags;
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_text_size::{TextRange, TextSize};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use smallvec::smallvec; use smallvec::{smallvec, SmallVec};
use ruff_python_ast::call_path::{collect_call_path, from_unqualified_name, CallPath}; use ruff_python_ast::call_path::{collect_call_path, from_unqualified_name, CallPath};
use ruff_python_ast::helpers::from_relative_import; use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_python_stdlib::path::is_python_stub_file; use ruff_python_stdlib::path::is_python_stub_file;
use ruff_python_stdlib::typing::is_typing_extension; use ruff_python_stdlib::typing::is_typing_extension;
use ruff_text_size::{TextRange, TextSize};
use crate::binding::{ use crate::binding::{
Binding, BindingFlags, BindingId, BindingKind, Bindings, Exceptions, FromImport, Import, Binding, BindingFlags, BindingId, BindingKind, Bindings, Exceptions, FromImport, Import,
@ -633,46 +632,32 @@ impl<'a> SemanticModel<'a> {
.or_else(|| self.find_binding(&head.id))?; .or_else(|| self.find_binding(&head.id))?;
match &binding.kind { match &binding.kind {
BindingKind::Import(Import { BindingKind::Import(Import { call_path, .. }) => {
qualified_name: name, let x = collect_call_path(value)?;
}) => { let (_, tail) = x.split_first()?;
let call_path = collect_call_path(value)?;
let (_, tail) = call_path.split_first()?;
let mut resolved = SmallVec::with_capacity(call_path.len() + tail.len());
resolved.extend_from_slice(call_path);
resolved.extend_from_slice(tail);
Some(resolved)
}
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name, .. }) => {
let x = collect_call_path(value)?;
let (_, tail) = x.split_first()?;
let name = qualified_name.split('.').next().unwrap_or(qualified_name);
let mut source_path: CallPath = from_unqualified_name(name); let mut source_path: CallPath = from_unqualified_name(name);
source_path.extend_from_slice(tail); source_path.extend_from_slice(tail);
Some(source_path) Some(source_path)
} }
BindingKind::SubmoduleImport(SubmoduleImport { BindingKind::FromImport(FromImport { call_path, .. }) => {
qualified_name: name, let x = collect_call_path(value)?;
}) => { let (_, tail) = x.split_first()?;
let call_path = collect_call_path(value)?;
let (_, tail) = call_path.split_first()?;
let name = name.split('.').next().unwrap_or(name); let mut resolved = SmallVec::with_capacity(call_path.len() + tail.len());
let mut source_path: CallPath = from_unqualified_name(name); resolved.extend_from_slice(call_path);
source_path.extend_from_slice(tail); resolved.extend_from_slice(tail);
Some(source_path) Some(resolved)
}
BindingKind::FromImport(FromImport {
qualified_name: name,
}) => {
let call_path = collect_call_path(value)?;
let (_, tail) = call_path.split_first()?;
if name.starts_with('.') {
let mut source_path = from_relative_import(self.module_path?, name);
if source_path.is_empty() {
None
} else {
source_path.extend_from_slice(tail);
Some(source_path)
}
} else {
let mut source_path: CallPath = from_unqualified_name(name);
source_path.extend_from_slice(tail);
Some(source_path)
}
} }
BindingKind::Builtin => Some(smallvec!["", head.id.as_str()]), BindingKind::Builtin => Some(smallvec!["", head.id.as_str()]),
_ => None, _ => None,
@ -703,7 +688,7 @@ impl<'a> SemanticModel<'a> {
// Ex) Given `module="sys"` and `object="exit"`: // Ex) Given `module="sys"` and `object="exit"`:
// `import sys` -> `sys.exit` // `import sys` -> `sys.exit`
// `import sys as sys2` -> `sys2.exit` // `import sys as sys2` -> `sys2.exit`
BindingKind::Import(Import { qualified_name }) => { BindingKind::Import(Import { qualified_name, .. }) => {
if qualified_name == &module { if qualified_name == &module {
if let Some(source) = binding.source { if let Some(source) = binding.source {
// Verify that `sys` isn't bound in an inner scope. // Verify that `sys` isn't bound in an inner scope.
@ -724,7 +709,7 @@ impl<'a> SemanticModel<'a> {
// Ex) Given `module="os.path"` and `object="join"`: // Ex) Given `module="os.path"` and `object="join"`:
// `from os.path import join` -> `join` // `from os.path import join` -> `join`
// `from os.path import join as join2` -> `join2` // `from os.path import join as join2` -> `join2`
BindingKind::FromImport(FromImport { qualified_name }) => { BindingKind::FromImport(FromImport { qualified_name, .. }) => {
if let Some((target_module, target_member)) = qualified_name.split_once('.') if let Some((target_module, target_member)) = qualified_name.split_once('.')
{ {
if target_module == module && target_member == member { if target_module == module && target_member == member {

6
foo.py Normal file
View File

@ -0,0 +1,6 @@
print(
"Unexpected changes:\n"
"\n".join(["1", "2", "3"])
)