mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
Split CallPath
into QualifiedName
and UnqualifiedName
(#10210)
## Summary Charlie can probably explain this better than I but it turns out, `CallPath` is used for two different things: * To represent unqualified names like `version` where `version` can be a local variable or imported (e.g. `from sys import version` where the full qualified name is `sys.version`) * To represent resolved, full qualified names This PR splits `CallPath` into two types to make this destinction clear. > Note: I haven't renamed all `call_path` variables to `qualified_name` or `unqualified_name`. I can do that if that's welcomed but I first want to get feedback on the approach and naming overall. ## Test Plan `cargo test`
This commit is contained in:
parent
ba4328226d
commit
a6d892b1f4
181 changed files with 1692 additions and 1412 deletions
|
@ -1,28 +1,28 @@
|
|||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::call_path::CallPath;
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
|
||||
use crate::{BindingId, SemanticModel};
|
||||
|
||||
/// Return `true` if any base class matches a [`CallPath`] predicate.
|
||||
pub fn any_call_path(
|
||||
/// Return `true` if any base class matches a [`QualifiedName`] predicate.
|
||||
pub fn any_qualified_name(
|
||||
class_def: &ast::StmtClassDef,
|
||||
semantic: &SemanticModel,
|
||||
func: &dyn Fn(CallPath) -> bool,
|
||||
func: &dyn Fn(QualifiedName) -> bool,
|
||||
) -> bool {
|
||||
fn inner(
|
||||
class_def: &ast::StmtClassDef,
|
||||
semantic: &SemanticModel,
|
||||
func: &dyn Fn(CallPath) -> bool,
|
||||
func: &dyn Fn(QualifiedName) -> bool,
|
||||
seen: &mut FxHashSet<BindingId>,
|
||||
) -> bool {
|
||||
class_def.bases().iter().any(|expr| {
|
||||
// If the base class itself matches the pattern, then this does too.
|
||||
// Ex) `class Foo(BaseModel): ...`
|
||||
if semantic
|
||||
.resolve_call_path(map_subscript(expr))
|
||||
.resolve_qualified_name(map_subscript(expr))
|
||||
.is_some_and(func)
|
||||
{
|
||||
return true;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use ruff_python_ast::call_path::CallPath;
|
||||
use ruff_python_ast::helpers::map_callable;
|
||||
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
|
||||
use ruff_python_ast::Decorator;
|
||||
|
||||
use crate::model::SemanticModel;
|
||||
|
@ -35,9 +35,9 @@ pub fn classify(
|
|||
|| class_def.bases().iter().any(|expr| {
|
||||
// The class itself extends a known metaclass, so all methods are class methods.
|
||||
semantic
|
||||
.resolve_call_path(map_callable(expr))
|
||||
.is_some_and( |call_path| {
|
||||
matches!(call_path.segments(), ["", "type"] | ["abc", "ABCMeta"])
|
||||
.resolve_qualified_name(map_callable(expr))
|
||||
.is_some_and( |qualified_name| {
|
||||
matches!(qualified_name.segments(), ["", "type"] | ["abc", "ABCMeta"])
|
||||
})
|
||||
})
|
||||
|| decorator_list.iter().any(|decorator| is_class_method(decorator, semantic, classmethod_decorators))
|
||||
|
@ -59,14 +59,14 @@ fn is_static_method(
|
|||
|
||||
// The decorator is an import, so should match against a qualified path.
|
||||
if semantic
|
||||
.resolve_call_path(decorator)
|
||||
.is_some_and(|call_path| {
|
||||
.resolve_qualified_name(decorator)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
["", "staticmethod"] | ["abc", "abstractstaticmethod"]
|
||||
) || staticmethod_decorators
|
||||
.iter()
|
||||
.any(|decorator| call_path == CallPath::from_qualified_name(decorator))
|
||||
.any(|decorator| qualified_name == QualifiedName::from_dotted_name(decorator))
|
||||
})
|
||||
{
|
||||
return true;
|
||||
|
@ -75,8 +75,8 @@ fn is_static_method(
|
|||
// We do not have a resolvable call path, most likely from a decorator like
|
||||
// `@someproperty.setter`. Instead, match on the last element.
|
||||
if !staticmethod_decorators.is_empty() {
|
||||
if CallPath::from_expr(decorator).is_some_and(|call_path| {
|
||||
call_path.segments().last().is_some_and(|tail| {
|
||||
if UnqualifiedName::from_expr(decorator).is_some_and(|name| {
|
||||
name.segments().last().is_some_and(|tail| {
|
||||
staticmethod_decorators
|
||||
.iter()
|
||||
.any(|decorator| tail == decorator)
|
||||
|
@ -99,14 +99,14 @@ fn is_class_method(
|
|||
|
||||
// The decorator is an import, so should match against a qualified path.
|
||||
if semantic
|
||||
.resolve_call_path(decorator)
|
||||
.is_some_and(|call_path| {
|
||||
.resolve_qualified_name(decorator)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
["", "classmethod"] | ["abc", "abstractclassmethod"]
|
||||
) || classmethod_decorators
|
||||
.iter()
|
||||
.any(|decorator| call_path == CallPath::from_qualified_name(decorator))
|
||||
.any(|decorator| qualified_name == QualifiedName::from_dotted_name(decorator))
|
||||
})
|
||||
{
|
||||
return true;
|
||||
|
@ -115,8 +115,8 @@ fn is_class_method(
|
|||
// We do not have a resolvable call path, most likely from a decorator like
|
||||
// `@someproperty.setter`. Instead, match on the last element.
|
||||
if !classmethod_decorators.is_empty() {
|
||||
if CallPath::from_expr(decorator).is_some_and(|call_path| {
|
||||
call_path.segments().last().is_some_and(|tail| {
|
||||
if UnqualifiedName::from_expr(decorator).is_some_and(|name| {
|
||||
name.segments().last().is_some_and(|tail| {
|
||||
classmethod_decorators
|
||||
.iter()
|
||||
.any(|decorator| tail == decorator)
|
||||
|
|
|
@ -17,10 +17,10 @@ pub fn is_sys_path_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool {
|
|||
return false;
|
||||
};
|
||||
semantic
|
||||
.resolve_call_path(func.as_ref())
|
||||
.is_some_and(|call_path| {
|
||||
.resolve_qualified_name(func.as_ref())
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
[
|
||||
"sys",
|
||||
"path",
|
||||
|
@ -47,10 +47,10 @@ pub fn is_os_environ_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool
|
|||
match stmt {
|
||||
Stmt::Expr(ast::StmtExpr { value, .. }) => match value.as_ref() {
|
||||
Expr::Call(ast::ExprCall { func, .. }) => semantic
|
||||
.resolve_call_path(func.as_ref())
|
||||
.is_some_and(|call_path| {
|
||||
.resolve_qualified_name(func.as_ref())
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
["os", "putenv" | "unsetenv"]
|
||||
| [
|
||||
"os",
|
||||
|
@ -63,20 +63,24 @@ pub fn is_os_environ_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool
|
|||
},
|
||||
Stmt::Delete(ast::StmtDelete { targets, .. }) => targets.iter().any(|target| {
|
||||
semantic
|
||||
.resolve_call_path(map_subscript(target))
|
||||
.is_some_and(|call_path| matches!(call_path.segments(), ["os", "environ"]))
|
||||
.resolve_qualified_name(map_subscript(target))
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["os", "environ"])
|
||||
})
|
||||
}),
|
||||
Stmt::Assign(ast::StmtAssign { targets, .. }) => targets.iter().any(|target| {
|
||||
semantic
|
||||
.resolve_call_path(map_subscript(target))
|
||||
.is_some_and(|call_path| matches!(call_path.segments(), ["os", "environ"]))
|
||||
.resolve_qualified_name(map_subscript(target))
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["os", "environ"])
|
||||
})
|
||||
}),
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => semantic
|
||||
.resolve_call_path(map_subscript(target))
|
||||
.is_some_and(|call_path| matches!(call_path.segments(), ["os", "environ"])),
|
||||
.resolve_qualified_name(map_subscript(target))
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["os", "environ"])),
|
||||
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => semantic
|
||||
.resolve_call_path(map_subscript(target))
|
||||
.is_some_and(|call_path| matches!(call_path.segments(), ["os", "environ"])),
|
||||
.resolve_qualified_name(map_subscript(target))
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["os", "environ"])),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +99,6 @@ pub fn is_matplotlib_activation(stmt: &Stmt, semantic: &SemanticModel) -> bool {
|
|||
return false;
|
||||
};
|
||||
semantic
|
||||
.resolve_call_path(func.as_ref())
|
||||
.is_some_and(|call_path| matches!(call_path.segments(), ["matplotlib", "use"]))
|
||||
.resolve_qualified_name(func.as_ref())
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["matplotlib", "use"]))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use ruff_python_ast::call_path::CallPath;
|
||||
use ruff_python_ast::helpers::is_const_true;
|
||||
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword};
|
||||
|
||||
use crate::model::SemanticModel;
|
||||
|
@ -27,9 +27,9 @@ pub fn is_logger_candidate(
|
|||
|
||||
// If the symbol was imported from another module, ensure that it's either a user-specified
|
||||
// logger object, the `logging` module itself, or `flask.current_app.logger`.
|
||||
if let Some(call_path) = semantic.resolve_call_path(value) {
|
||||
if let Some(qualified_name) = semantic.resolve_qualified_name(value) {
|
||||
if matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
["logging"] | ["flask", "current_app", "logger"]
|
||||
) {
|
||||
return true;
|
||||
|
@ -37,7 +37,7 @@ pub fn is_logger_candidate(
|
|||
|
||||
if logger_objects
|
||||
.iter()
|
||||
.any(|logger| CallPath::from_qualified_name(logger) == call_path)
|
||||
.any(|logger| QualifiedName::from_dotted_name(logger) == qualified_name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ pub fn is_logger_candidate(
|
|||
|
||||
// Otherwise, if the symbol was defined in the current module, match against some common
|
||||
// logger names.
|
||||
if let Some(call_path) = CallPath::from_expr(value) {
|
||||
if let Some(tail) = call_path.segments().last() {
|
||||
if let Some(name) = UnqualifiedName::from_expr(value) {
|
||||
if let Some(tail) = name.segments().last() {
|
||||
if tail.starts_with("log")
|
||||
|| tail.ends_with("logger")
|
||||
|| tail.ends_with("logging")
|
||||
|
@ -78,8 +78,8 @@ pub fn exc_info<'a>(arguments: &'a Arguments, semantic: &SemanticModel) -> Optio
|
|||
if exc_info
|
||||
.value
|
||||
.as_call_expr()
|
||||
.and_then(|call| semantic.resolve_call_path(&call.func))
|
||||
.is_some_and(|call_path| matches!(call_path.segments(), ["sys", "exc_info"]))
|
||||
.and_then(|call| semantic.resolve_qualified_name(&call.func))
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["sys", "exc_info"]))
|
||||
{
|
||||
return Some(exc_info);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Analysis rules for the `typing` module.
|
||||
|
||||
use ruff_python_ast::call_path::CallPath;
|
||||
use ruff_python_ast::helpers::{any_over_expr, is_const_false, map_subscript};
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::{self as ast, Expr, Int, Operator, ParameterWithDefault, Parameters, Stmt};
|
||||
use ruff_python_stdlib::typing::{
|
||||
as_pep_585_generic, has_pep_585_generic, is_immutable_generic_type,
|
||||
|
@ -42,43 +42,45 @@ pub fn match_annotated_subscript<'a>(
|
|||
typing_modules: impl Iterator<Item = &'a str>,
|
||||
extend_generics: &[String],
|
||||
) -> Option<SubscriptKind> {
|
||||
semantic.resolve_call_path(expr).and_then(|call_path| {
|
||||
if is_standard_library_literal(call_path.segments()) {
|
||||
return Some(SubscriptKind::Literal);
|
||||
}
|
||||
semantic
|
||||
.resolve_qualified_name(expr)
|
||||
.and_then(|qualified_name| {
|
||||
if is_standard_library_literal(qualified_name.segments()) {
|
||||
return Some(SubscriptKind::Literal);
|
||||
}
|
||||
|
||||
if is_standard_library_generic(call_path.segments())
|
||||
|| extend_generics
|
||||
.iter()
|
||||
.map(|target| CallPath::from_qualified_name(target))
|
||||
.any(|target| call_path == target)
|
||||
{
|
||||
return Some(SubscriptKind::Generic);
|
||||
}
|
||||
if is_standard_library_generic(qualified_name.segments())
|
||||
|| extend_generics
|
||||
.iter()
|
||||
.map(|target| QualifiedName::from_dotted_name(target))
|
||||
.any(|target| qualified_name == target)
|
||||
{
|
||||
return Some(SubscriptKind::Generic);
|
||||
}
|
||||
|
||||
if is_pep_593_generic_type(call_path.segments()) {
|
||||
return Some(SubscriptKind::PEP593Annotation);
|
||||
}
|
||||
if is_pep_593_generic_type(qualified_name.segments()) {
|
||||
return Some(SubscriptKind::PEP593Annotation);
|
||||
}
|
||||
|
||||
for module in typing_modules {
|
||||
let module_call_path: CallPath = CallPath::from_unqualified_name(module);
|
||||
if call_path.starts_with(&module_call_path) {
|
||||
if let Some(member) = call_path.segments().last() {
|
||||
if is_literal_member(member) {
|
||||
return Some(SubscriptKind::Literal);
|
||||
}
|
||||
if is_standard_library_generic_member(member) {
|
||||
return Some(SubscriptKind::Generic);
|
||||
}
|
||||
if is_pep_593_generic_member(member) {
|
||||
return Some(SubscriptKind::PEP593Annotation);
|
||||
for module in typing_modules {
|
||||
let module_qualified_name = QualifiedName::imported(module);
|
||||
if qualified_name.starts_with(&module_qualified_name) {
|
||||
if let Some(member) = qualified_name.segments().last() {
|
||||
if is_literal_member(member) {
|
||||
return Some(SubscriptKind::Literal);
|
||||
}
|
||||
if is_standard_library_generic_member(member) {
|
||||
return Some(SubscriptKind::Generic);
|
||||
}
|
||||
if is_pep_593_generic_member(member) {
|
||||
return Some(SubscriptKind::PEP593Annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
|
@ -103,10 +105,10 @@ impl std::fmt::Display for ModuleMember {
|
|||
pub fn to_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> Option<ModuleMember> {
|
||||
semantic
|
||||
.seen_module(Modules::TYPING | Modules::TYPING_EXTENSIONS)
|
||||
.then(|| semantic.resolve_call_path(expr))
|
||||
.then(|| semantic.resolve_qualified_name(expr))
|
||||
.flatten()
|
||||
.and_then(|call_path| {
|
||||
let [module, member] = call_path.segments() else {
|
||||
.and_then(|qualified_name| {
|
||||
let [module, member] = qualified_name.segments() else {
|
||||
return None;
|
||||
};
|
||||
as_pep_585_generic(module, member).map(|(module, member)| {
|
||||
|
@ -121,12 +123,14 @@ pub fn to_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> Option<Module
|
|||
|
||||
/// Return whether a given expression uses a PEP 585 standard library generic.
|
||||
pub fn is_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic.resolve_call_path(expr).is_some_and(|call_path| {
|
||||
let [module, name] = call_path.segments() else {
|
||||
return false;
|
||||
};
|
||||
has_pep_585_generic(module, name)
|
||||
})
|
||||
semantic
|
||||
.resolve_qualified_name(expr)
|
||||
.is_some_and(|qualified_name| {
|
||||
let [module, name] = qualified_name.segments() else {
|
||||
return false;
|
||||
};
|
||||
has_pep_585_generic(module, name)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
|
@ -195,12 +199,12 @@ pub fn to_pep604_operator(
|
|||
}
|
||||
|
||||
semantic
|
||||
.resolve_call_path(value)
|
||||
.resolve_qualified_name(value)
|
||||
.as_ref()
|
||||
.and_then(|call_path| {
|
||||
if semantic.match_typing_call_path(call_path, "Optional") {
|
||||
.and_then(|qualified_name| {
|
||||
if semantic.match_typing_qualified_name(qualified_name, "Optional") {
|
||||
Some(Pep604Operator::Optional)
|
||||
} else if semantic.match_typing_call_path(call_path, "Union") {
|
||||
} else if semantic.match_typing_qualified_name(qualified_name, "Union") {
|
||||
Some(Pep604Operator::Union)
|
||||
} else {
|
||||
None
|
||||
|
@ -213,23 +217,26 @@ pub fn to_pep604_operator(
|
|||
pub fn is_immutable_annotation(
|
||||
expr: &Expr,
|
||||
semantic: &SemanticModel,
|
||||
extend_immutable_calls: &[CallPath],
|
||||
extend_immutable_calls: &[QualifiedName],
|
||||
) -> bool {
|
||||
match expr {
|
||||
Expr::Name(_) | Expr::Attribute(_) => {
|
||||
semantic.resolve_call_path(expr).is_some_and(|call_path| {
|
||||
is_immutable_non_generic_type(call_path.segments())
|
||||
|| is_immutable_generic_type(call_path.segments())
|
||||
|| extend_immutable_calls
|
||||
.iter()
|
||||
.any(|target| call_path == *target)
|
||||
})
|
||||
semantic
|
||||
.resolve_qualified_name(expr)
|
||||
.is_some_and(|qualified_name| {
|
||||
is_immutable_non_generic_type(qualified_name.segments())
|
||||
|| is_immutable_generic_type(qualified_name.segments())
|
||||
|| extend_immutable_calls
|
||||
.iter()
|
||||
.any(|target| qualified_name == *target)
|
||||
})
|
||||
}
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||
semantic.resolve_call_path(value).is_some_and(|call_path| {
|
||||
if is_immutable_generic_type(call_path.segments()) {
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => semantic
|
||||
.resolve_qualified_name(value)
|
||||
.is_some_and(|qualified_name| {
|
||||
if is_immutable_generic_type(qualified_name.segments()) {
|
||||
true
|
||||
} else if matches!(call_path.segments(), ["typing", "Union"]) {
|
||||
} else if matches!(qualified_name.segments(), ["typing", "Union"]) {
|
||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||
elts.iter().all(|elt| {
|
||||
is_immutable_annotation(elt, semantic, extend_immutable_calls)
|
||||
|
@ -237,9 +244,9 @@ pub fn is_immutable_annotation(
|
|||
} else {
|
||||
false
|
||||
}
|
||||
} else if matches!(call_path.segments(), ["typing", "Optional"]) {
|
||||
} else if matches!(qualified_name.segments(), ["typing", "Optional"]) {
|
||||
is_immutable_annotation(slice, semantic, extend_immutable_calls)
|
||||
} else if is_pep_593_generic_type(call_path.segments()) {
|
||||
} else if is_pep_593_generic_type(qualified_name.segments()) {
|
||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||
elts.first().is_some_and(|elt| {
|
||||
is_immutable_annotation(elt, semantic, extend_immutable_calls)
|
||||
|
@ -250,8 +257,7 @@ pub fn is_immutable_annotation(
|
|||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left,
|
||||
op: Operator::BitOr,
|
||||
|
@ -270,22 +276,24 @@ pub fn is_immutable_annotation(
|
|||
pub fn is_immutable_func(
|
||||
func: &Expr,
|
||||
semantic: &SemanticModel,
|
||||
extend_immutable_calls: &[CallPath],
|
||||
extend_immutable_calls: &[QualifiedName],
|
||||
) -> bool {
|
||||
semantic.resolve_call_path(func).is_some_and(|call_path| {
|
||||
is_immutable_return_type(call_path.segments())
|
||||
|| extend_immutable_calls
|
||||
.iter()
|
||||
.any(|target| call_path == *target)
|
||||
})
|
||||
semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| {
|
||||
is_immutable_return_type(qualified_name.segments())
|
||||
|| extend_immutable_calls
|
||||
.iter()
|
||||
.any(|target| qualified_name == *target)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if `func` is a function that returns a mutable value.
|
||||
pub fn is_mutable_func(func: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
.resolve_call_path(func)
|
||||
.resolve_qualified_name(func)
|
||||
.as_ref()
|
||||
.map(CallPath::segments)
|
||||
.map(QualifiedName::segments)
|
||||
.is_some_and(is_mutable_return_type)
|
||||
}
|
||||
|
||||
|
@ -336,9 +344,14 @@ pub fn is_sys_version_block(stmt: &ast::StmtIf, semantic: &SemanticModel) -> boo
|
|||
let ast::StmtIf { test, .. } = stmt;
|
||||
|
||||
any_over_expr(test, &|expr| {
|
||||
semantic.resolve_call_path(expr).is_some_and(|call_path| {
|
||||
matches!(call_path.segments(), ["sys", "version_info" | "platform"])
|
||||
})
|
||||
semantic
|
||||
.resolve_qualified_name(expr)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["sys", "version_info" | "platform"]
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -608,19 +621,19 @@ pub struct IoBaseChecker;
|
|||
impl TypeChecker for IoBaseChecker {
|
||||
fn match_annotation(annotation: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
.resolve_call_path(annotation)
|
||||
.is_some_and(|call_path| {
|
||||
if semantic.match_typing_call_path(&call_path, "IO") {
|
||||
.resolve_qualified_name(annotation)
|
||||
.is_some_and(|qualified_name| {
|
||||
if semantic.match_typing_qualified_name(&qualified_name, "IO") {
|
||||
return true;
|
||||
}
|
||||
if semantic.match_typing_call_path(&call_path, "BinaryIO") {
|
||||
if semantic.match_typing_qualified_name(&qualified_name, "BinaryIO") {
|
||||
return true;
|
||||
}
|
||||
if semantic.match_typing_call_path(&call_path, "TextIO") {
|
||||
if semantic.match_typing_qualified_name(&qualified_name, "TextIO") {
|
||||
return true;
|
||||
}
|
||||
matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
[
|
||||
"io",
|
||||
"IOBase"
|
||||
|
@ -652,25 +665,27 @@ impl TypeChecker for IoBaseChecker {
|
|||
if let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
|
||||
if attr.as_str() == "open" {
|
||||
if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
|
||||
return semantic.resolve_call_path(func).is_some_and(|call_path| {
|
||||
matches!(
|
||||
call_path.segments(),
|
||||
[
|
||||
"pathlib",
|
||||
"Path" | "PurePath" | "PurePosixPath" | "PureWindowsPath"
|
||||
]
|
||||
)
|
||||
});
|
||||
return semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
[
|
||||
"pathlib",
|
||||
"Path" | "PurePath" | "PurePosixPath" | "PureWindowsPath"
|
||||
]
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ex) `open("file.txt")`
|
||||
semantic
|
||||
.resolve_call_path(func.as_ref())
|
||||
.is_some_and(|call_path| {
|
||||
.resolve_qualified_name(func.as_ref())
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
["io", "open" | "open_code"] | ["os" | "", "open"]
|
||||
)
|
||||
})
|
||||
|
@ -736,7 +751,7 @@ fn find_parameter<'a>(
|
|||
.find(|arg| arg.parameter.name.range() == binding.range())
|
||||
}
|
||||
|
||||
/// Return the [`CallPath`] of the value to which the given [`Expr`] is assigned, if any.
|
||||
/// Return the [`QualifiedName`] of the value to which the given [`Expr`] is assigned, if any.
|
||||
///
|
||||
/// For example, given:
|
||||
/// ```python
|
||||
|
@ -750,20 +765,20 @@ fn find_parameter<'a>(
|
|||
pub fn resolve_assignment<'a>(
|
||||
expr: &'a Expr,
|
||||
semantic: &'a SemanticModel<'a>,
|
||||
) -> Option<CallPath<'a>> {
|
||||
) -> Option<QualifiedName<'a>> {
|
||||
let name = expr.as_name_expr()?;
|
||||
let binding_id = semantic.resolve_name(name)?;
|
||||
let statement = semantic.binding(binding_id).statement(semantic)?;
|
||||
match statement {
|
||||
Stmt::Assign(ast::StmtAssign { value, .. }) => {
|
||||
let ast::ExprCall { func, .. } = value.as_call_expr()?;
|
||||
semantic.resolve_call_path(func)
|
||||
semantic.resolve_qualified_name(func)
|
||||
}
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
value: Some(value), ..
|
||||
}) => {
|
||||
let ast::ExprCall { func, .. } = value.as_call_expr()?;
|
||||
semantic.resolve_call_path(func)
|
||||
semantic.resolve_qualified_name(func)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ use std::path::Path;
|
|||
|
||||
use ruff_python_ast::{self as ast, Decorator};
|
||||
|
||||
use ruff_python_ast::call_path::CallPath;
|
||||
use ruff_python_ast::helpers::map_callable;
|
||||
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
|
||||
|
||||
use crate::model::SemanticModel;
|
||||
|
||||
|
@ -17,8 +17,8 @@ pub enum Visibility {
|
|||
pub fn is_staticmethod(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool {
|
||||
decorator_list.iter().any(|decorator| {
|
||||
semantic
|
||||
.resolve_call_path(map_callable(&decorator.expression))
|
||||
.is_some_and(|call_path| matches!(call_path.segments(), ["", "staticmethod"]))
|
||||
.resolve_qualified_name(map_callable(&decorator.expression))
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["", "staticmethod"]))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,8 @@ pub fn is_staticmethod(decorator_list: &[Decorator], semantic: &SemanticModel) -
|
|||
pub fn is_classmethod(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool {
|
||||
decorator_list.iter().any(|decorator| {
|
||||
semantic
|
||||
.resolve_call_path(map_callable(&decorator.expression))
|
||||
.is_some_and(|call_path| matches!(call_path.segments(), ["", "classmethod"]))
|
||||
.resolve_qualified_name(map_callable(&decorator.expression))
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["", "classmethod"]))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -49,10 +49,10 @@ pub fn is_override(decorator_list: &[Decorator], semantic: &SemanticModel) -> bo
|
|||
pub fn is_abstract(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool {
|
||||
decorator_list.iter().any(|decorator| {
|
||||
semantic
|
||||
.resolve_call_path(map_callable(&decorator.expression))
|
||||
.is_some_and(|call_path| {
|
||||
.resolve_qualified_name(map_callable(&decorator.expression))
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
[
|
||||
"abc",
|
||||
"abstractmethod"
|
||||
|
@ -70,19 +70,19 @@ pub fn is_abstract(decorator_list: &[Decorator], semantic: &SemanticModel) -> bo
|
|||
/// `@property`-like decorators.
|
||||
pub fn is_property(
|
||||
decorator_list: &[Decorator],
|
||||
extra_properties: &[CallPath],
|
||||
extra_properties: &[QualifiedName],
|
||||
semantic: &SemanticModel,
|
||||
) -> bool {
|
||||
decorator_list.iter().any(|decorator| {
|
||||
semantic
|
||||
.resolve_call_path(map_callable(&decorator.expression))
|
||||
.is_some_and(|call_path| {
|
||||
.resolve_qualified_name(map_callable(&decorator.expression))
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
["", "property"] | ["functools", "cached_property"]
|
||||
) || extra_properties
|
||||
.iter()
|
||||
.any(|extra_property| extra_property.segments() == call_path.segments())
|
||||
.any(|extra_property| extra_property.segments() == qualified_name.segments())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -187,9 +187,9 @@ pub(crate) fn function_visibility(function: &ast::StmtFunctionDef) -> Visibility
|
|||
pub fn method_visibility(function: &ast::StmtFunctionDef) -> Visibility {
|
||||
// Is this a setter or deleter?
|
||||
if function.decorator_list.iter().any(|decorator| {
|
||||
CallPath::from_expr(&decorator.expression).is_some_and(|call_path| {
|
||||
call_path.segments() == [function.name.as_str(), "setter"]
|
||||
|| call_path.segments() == [function.name.as_str(), "deleter"]
|
||||
UnqualifiedName::from_expr(&decorator.expression).is_some_and(|name| {
|
||||
name.segments() == [function.name.as_str(), "setter"]
|
||||
|| name.segments() == [function.name.as_str(), "deleter"]
|
||||
})
|
||||
}) {
|
||||
return Visibility::Private;
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::ops::{Deref, DerefMut};
|
|||
use bitflags::bitflags;
|
||||
|
||||
use ruff_index::{newtype_index, IndexSlice, IndexVec};
|
||||
use ruff_python_ast::call_path::format_call_path_segments;
|
||||
use ruff_python_ast::name::format_qualified_name_segments;
|
||||
use ruff_python_ast::Stmt;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
@ -125,38 +125,38 @@ impl<'a> Binding<'a> {
|
|||
// import foo.baz
|
||||
// ```
|
||||
BindingKind::Import(Import {
|
||||
call_path: redefinition,
|
||||
qualified_name: redefinition,
|
||||
}) => {
|
||||
if let BindingKind::SubmoduleImport(SubmoduleImport {
|
||||
call_path: definition,
|
||||
qualified_name: definition,
|
||||
}) = &existing.kind
|
||||
{
|
||||
return redefinition == definition;
|
||||
}
|
||||
}
|
||||
BindingKind::FromImport(FromImport {
|
||||
call_path: redefinition,
|
||||
qualified_name: redefinition,
|
||||
}) => {
|
||||
if let BindingKind::SubmoduleImport(SubmoduleImport {
|
||||
call_path: definition,
|
||||
qualified_name: definition,
|
||||
}) = &existing.kind
|
||||
{
|
||||
return redefinition == definition;
|
||||
}
|
||||
}
|
||||
BindingKind::SubmoduleImport(SubmoduleImport {
|
||||
call_path: redefinition,
|
||||
qualified_name: redefinition,
|
||||
}) => match &existing.kind {
|
||||
BindingKind::Import(Import {
|
||||
call_path: definition,
|
||||
qualified_name: definition,
|
||||
})
|
||||
| BindingKind::SubmoduleImport(SubmoduleImport {
|
||||
call_path: definition,
|
||||
qualified_name: definition,
|
||||
}) => {
|
||||
return redefinition == definition;
|
||||
}
|
||||
BindingKind::FromImport(FromImport {
|
||||
call_path: definition,
|
||||
qualified_name: definition,
|
||||
}) => {
|
||||
return redefinition == definition;
|
||||
}
|
||||
|
@ -362,7 +362,7 @@ pub struct Import<'a> {
|
|||
/// The full name of the module being imported.
|
||||
/// Ex) Given `import foo`, `qualified_name` would be "foo".
|
||||
/// Ex) Given `import foo as bar`, `qualified_name` would be "foo".
|
||||
pub call_path: Box<[&'a str]>,
|
||||
pub qualified_name: Box<[&'a str]>,
|
||||
}
|
||||
|
||||
/// A binding for a member imported from a module, keyed on the name to which the member is bound.
|
||||
|
@ -373,7 +373,7 @@ pub struct FromImport<'a> {
|
|||
/// 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 as baz`, `qualified_name` would be "foo.bar".
|
||||
pub call_path: Box<[&'a str]>,
|
||||
pub qualified_name: Box<[&'a str]>,
|
||||
}
|
||||
|
||||
/// A binding for a submodule imported from a module, keyed on the name of the parent module.
|
||||
|
@ -382,7 +382,7 @@ pub struct FromImport<'a> {
|
|||
pub struct SubmoduleImport<'a> {
|
||||
/// The full name of the submodule being imported.
|
||||
/// Ex) Given `import foo.bar`, `qualified_name` would be "foo.bar".
|
||||
pub call_path: Box<[&'a str]>,
|
||||
pub qualified_name: Box<[&'a str]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, is_macro::Is)]
|
||||
|
@ -561,7 +561,7 @@ pub trait Imported<'a> {
|
|||
/// Returns the fully-qualified name of the imported symbol.
|
||||
fn qualified_name(&self) -> String {
|
||||
let mut output = String::new();
|
||||
format_call_path_segments(self.call_path(), &mut output).unwrap();
|
||||
format_qualified_name_segments(self.call_path(), &mut output).unwrap();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
@ -569,12 +569,12 @@ pub trait Imported<'a> {
|
|||
impl<'a> Imported<'a> for Import<'a> {
|
||||
/// For example, given `import foo`, returns `["foo"]`.
|
||||
fn call_path(&self) -> &[&'a str] {
|
||||
self.call_path.as_ref()
|
||||
self.qualified_name.as_ref()
|
||||
}
|
||||
|
||||
/// For example, given `import foo`, returns `["foo"]`.
|
||||
fn module_name(&self) -> &[&'a str] {
|
||||
&self.call_path[..1]
|
||||
&self.qualified_name[..1]
|
||||
}
|
||||
|
||||
/// For example, given `import foo`, returns `"foo"`.
|
||||
|
@ -586,12 +586,12 @@ impl<'a> Imported<'a> for Import<'a> {
|
|||
impl<'a> Imported<'a> for SubmoduleImport<'a> {
|
||||
/// For example, given `import foo.bar`, returns `["foo", "bar"]`.
|
||||
fn call_path(&self) -> &[&'a str] {
|
||||
self.call_path.as_ref()
|
||||
self.qualified_name.as_ref()
|
||||
}
|
||||
|
||||
/// For example, given `import foo.bar`, returns `["foo"]`.
|
||||
fn module_name(&self) -> &[&'a str] {
|
||||
&self.call_path[..1]
|
||||
&self.qualified_name[..1]
|
||||
}
|
||||
|
||||
/// For example, given `import foo.bar`, returns `"foo.bar"`.
|
||||
|
@ -603,17 +603,17 @@ impl<'a> Imported<'a> for SubmoduleImport<'a> {
|
|||
impl<'a> Imported<'a> for FromImport<'a> {
|
||||
/// For example, given `from foo import bar`, returns `["foo", "bar"]`.
|
||||
fn call_path(&self) -> &[&'a str] {
|
||||
&self.call_path
|
||||
&self.qualified_name
|
||||
}
|
||||
|
||||
/// For example, given `from foo import bar`, returns `["foo"]`.
|
||||
fn module_name(&self) -> &[&'a str] {
|
||||
&self.call_path[..self.call_path.len() - 1]
|
||||
&self.qualified_name[..self.qualified_name.len() - 1]
|
||||
}
|
||||
|
||||
/// For example, given `from foo import bar`, returns `"bar"`.
|
||||
fn member_name(&self) -> Cow<'a, str> {
|
||||
Cow::Borrowed(self.call_path[self.call_path.len() - 1])
|
||||
Cow::Borrowed(self.qualified_name[self.qualified_name.len() - 1])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ use std::path::Path;
|
|||
use bitflags::bitflags;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_python_ast::call_path::{CallPath, CallPathBuilder};
|
||||
use ruff_python_ast::helpers::from_relative_import;
|
||||
use ruff_python_ast::name::{QualifiedName, QualifiedNameBuilder, UnqualifiedName};
|
||||
use ruff_python_ast::{self as ast, Expr, Operator, Stmt};
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
@ -173,25 +173,31 @@ impl<'a> SemanticModel<'a> {
|
|||
pub fn match_typing_expr(&self, expr: &Expr, target: &str) -> bool {
|
||||
self.seen_typing()
|
||||
&& self
|
||||
.resolve_call_path(expr)
|
||||
.is_some_and(|call_path| self.match_typing_call_path(&call_path, target))
|
||||
.resolve_qualified_name(expr)
|
||||
.is_some_and(|qualified_name| {
|
||||
self.match_typing_qualified_name(&qualified_name, target)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the call path is a reference to `typing.${target}`.
|
||||
pub fn match_typing_call_path(&self, call_path: &CallPath, target: &str) -> bool {
|
||||
pub fn match_typing_qualified_name(
|
||||
&self,
|
||||
qualified_name: &QualifiedName,
|
||||
target: &str,
|
||||
) -> bool {
|
||||
if matches!(
|
||||
call_path.segments(),
|
||||
qualified_name.segments(),
|
||||
["typing" | "_typeshed" | "typing_extensions", member] if *member == target
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.typing_modules.iter().any(|module| {
|
||||
let module = CallPath::from_unqualified_name(module);
|
||||
let mut builder = CallPathBuilder::from_path(module);
|
||||
let module = QualifiedName::from_dotted_name(module);
|
||||
let mut builder = QualifiedNameBuilder::from_qualified_name(module);
|
||||
builder.push(target);
|
||||
let target_path = builder.build();
|
||||
call_path == &target_path
|
||||
qualified_name == &target_path
|
||||
}) {
|
||||
return true;
|
||||
}
|
||||
|
@ -568,10 +574,10 @@ impl<'a> SemanticModel<'a> {
|
|||
/// associated with `Class`, then the `BindingKind::FunctionDefinition` associated with
|
||||
/// `Class.method`.
|
||||
pub fn lookup_attribute(&self, value: &Expr) -> Option<BindingId> {
|
||||
let call_path = CallPath::from_expr(value)?;
|
||||
let unqualified_name = UnqualifiedName::from_expr(value)?;
|
||||
|
||||
// Find the symbol in the current scope.
|
||||
let (symbol, attribute) = call_path.segments().split_first()?;
|
||||
let (symbol, attribute) = unqualified_name.segments().split_first()?;
|
||||
let mut binding_id = self.lookup_symbol(symbol)?;
|
||||
|
||||
// Recursively resolve class attributes, e.g., `foo.bar.baz` in.
|
||||
|
@ -659,10 +665,10 @@ impl<'a> SemanticModel<'a> {
|
|||
/// ```
|
||||
///
|
||||
/// ...then `resolve_call_path(${python_version})` will resolve to `sys.version_info`.
|
||||
pub fn resolve_call_path<'name, 'expr: 'name>(
|
||||
pub fn resolve_qualified_name<'name, 'expr: 'name>(
|
||||
&self,
|
||||
value: &'expr Expr,
|
||||
) -> Option<CallPath<'name>>
|
||||
) -> Option<QualifiedName<'name>>
|
||||
where
|
||||
'a: 'name,
|
||||
{
|
||||
|
@ -683,53 +689,61 @@ impl<'a> SemanticModel<'a> {
|
|||
.map(|id| self.binding(id))?;
|
||||
|
||||
match &binding.kind {
|
||||
BindingKind::Import(Import { call_path }) => {
|
||||
let value_path = CallPath::from_expr(value)?;
|
||||
let (_, tail) = value_path.segments().split_first()?;
|
||||
let resolved: CallPath = call_path.iter().chain(tail.iter()).copied().collect();
|
||||
BindingKind::Import(Import { qualified_name }) => {
|
||||
let unqualified_name = UnqualifiedName::from_expr(value)?;
|
||||
let (_, tail) = unqualified_name.segments().split_first()?;
|
||||
let resolved: QualifiedName =
|
||||
qualified_name.iter().chain(tail.iter()).copied().collect();
|
||||
Some(resolved)
|
||||
}
|
||||
BindingKind::SubmoduleImport(SubmoduleImport { call_path }) => {
|
||||
let value_path = CallPath::from_expr(value)?;
|
||||
let (_, tail) = value_path.segments().split_first()?;
|
||||
let mut builder = CallPathBuilder::with_capacity(1 + tail.len());
|
||||
builder.extend(call_path.iter().copied().take(1));
|
||||
builder.extend(tail.iter().copied());
|
||||
Some(builder.build())
|
||||
}
|
||||
BindingKind::FromImport(FromImport { call_path }) => {
|
||||
let value_path = CallPath::from_expr(value)?;
|
||||
let (_, tail) = value_path.segments().split_first()?;
|
||||
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }) => {
|
||||
let value_name = UnqualifiedName::from_expr(value)?;
|
||||
let (_, tail) = value_name.segments().split_first()?;
|
||||
|
||||
let resolved: CallPath =
|
||||
if call_path.first().map_or(false, |segment| *segment == ".") {
|
||||
from_relative_import(self.module_path?, call_path, tail)?
|
||||
} else {
|
||||
call_path.iter().chain(tail.iter()).copied().collect()
|
||||
};
|
||||
Some(
|
||||
qualified_name
|
||||
.iter()
|
||||
.take(1)
|
||||
.chain(tail.iter())
|
||||
.copied()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
BindingKind::FromImport(FromImport { qualified_name }) => {
|
||||
let value_name = UnqualifiedName::from_expr(value)?;
|
||||
let (_, tail) = value_name.segments().split_first()?;
|
||||
|
||||
let resolved: QualifiedName = if qualified_name
|
||||
.first()
|
||||
.map_or(false, |segment| *segment == ".")
|
||||
{
|
||||
from_relative_import(self.module_path?, qualified_name, tail)?
|
||||
} else {
|
||||
qualified_name.iter().chain(tail.iter()).copied().collect()
|
||||
};
|
||||
Some(resolved)
|
||||
}
|
||||
BindingKind::Builtin => {
|
||||
if value.is_name_expr() {
|
||||
// Ex) `dict`
|
||||
Some(CallPath::from_slice(&["", head.id.as_str()]))
|
||||
Some(QualifiedName::from_slice(&["", head.id.as_str()]))
|
||||
} else {
|
||||
// Ex) `dict.__dict__`
|
||||
let value_path = CallPath::from_expr(value)?;
|
||||
let value_name = UnqualifiedName::from_expr(value)?;
|
||||
Some(
|
||||
std::iter::once("")
|
||||
.chain(value_path.segments().iter().copied())
|
||||
.chain(value_name.segments().iter().copied())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
BindingKind::ClassDefinition(_) | BindingKind::FunctionDefinition(_) => {
|
||||
let value_path = CallPath::from_expr(value)?;
|
||||
let resolved: CallPath = self
|
||||
let value_name = UnqualifiedName::from_expr(value)?;
|
||||
let resolved: QualifiedName = self
|
||||
.module_path?
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.chain(value_path.segments().iter().copied())
|
||||
.chain(value_name.segments().iter().copied())
|
||||
.collect();
|
||||
Some(resolved)
|
||||
}
|
||||
|
@ -765,8 +779,8 @@ impl<'a> SemanticModel<'a> {
|
|||
// Ex) Given `module="sys"` and `object="exit"`:
|
||||
// `import sys` -> `sys.exit`
|
||||
// `import sys as sys2` -> `sys2.exit`
|
||||
BindingKind::Import(Import { call_path }) => {
|
||||
if call_path.as_ref() == module_path.as_slice() {
|
||||
BindingKind::Import(Import { qualified_name }) => {
|
||||
if qualified_name.as_ref() == module_path.as_slice() {
|
||||
if let Some(source) = binding.source {
|
||||
// Verify that `sys` isn't bound in an inner scope.
|
||||
if self
|
||||
|
@ -787,8 +801,10 @@ impl<'a> SemanticModel<'a> {
|
|||
// Ex) Given `module="os.path"` and `object="join"`:
|
||||
// `from os.path import join` -> `join`
|
||||
// `from os.path import join as join2` -> `join2`
|
||||
BindingKind::FromImport(FromImport { call_path }) => {
|
||||
if let Some((target_member, target_module)) = call_path.split_last() {
|
||||
BindingKind::FromImport(FromImport { qualified_name }) => {
|
||||
if let Some((target_member, target_module)) =
|
||||
qualified_name.split_last()
|
||||
{
|
||||
if target_module == module_path.as_slice()
|
||||
&& target_member == &member
|
||||
{
|
||||
|
@ -814,8 +830,8 @@ impl<'a> SemanticModel<'a> {
|
|||
// `import os.path ` -> `os.name`
|
||||
// Ex) Given `module="os.path"` and `object="join"`:
|
||||
// `import os.path ` -> `os.path.join`
|
||||
BindingKind::SubmoduleImport(SubmoduleImport { call_path }) => {
|
||||
if call_path.starts_with(&module_path) {
|
||||
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }) => {
|
||||
if qualified_name.starts_with(&module_path) {
|
||||
if let Some(source) = binding.source {
|
||||
// Verify that `os` isn't bound in an inner scope.
|
||||
if self
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue