Skip to content

Commit

Permalink
Split CallPath into QualifiedName and UnqualifiedName
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Mar 3, 2024
1 parent e725b6f commit bcb457d
Show file tree
Hide file tree
Showing 177 changed files with 1,064 additions and 918 deletions.
100 changes: 56 additions & 44 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,11 +421,11 @@ where
self.semantic.add_module(module);

if alias.asname.is_none() && alias.name.contains('.') {
let call_path: Box<[&str]> = alias.name.split('.').collect();
let qualified_name: Box<[&str]> = alias.name.split('.').collect();
self.add_binding(
module,
alias.identifier(),
BindingKind::SubmoduleImport(SubmoduleImport { call_path }),
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }),
BindingFlags::EXTERNAL,
);
} else {
Expand All @@ -442,11 +442,11 @@ where
}

let name = alias.asname.as_ref().unwrap_or(&alias.name);
let call_path: Box<[&str]> = alias.name.split('.').collect();
let qualified_name: Box<[&str]> = alias.name.split('.').collect();
self.add_binding(
name,
alias.identifier(),
BindingKind::Import(Import { call_path }),
BindingKind::Import(Import { qualified_name }),
flags,
);
}
Expand Down Expand Up @@ -506,12 +506,12 @@ where
// Attempt to resolve any relative imports; but if we don't know the current
// module path, or the relative import extends beyond the package root,
// fallback to a literal representation (e.g., `[".", "foo"]`).
let call_path = collect_import_from_member(level, module, &alias.name)
let qualified_name = collect_import_from_member(level, module, &alias.name)
.into_boxed_slice();
self.add_binding(
name,
alias.identifier(),
BindingKind::FromImport(FromImport { call_path }),
BindingKind::FromImport(FromImport { qualified_name }),
flags,
);
}
Expand Down Expand Up @@ -754,8 +754,8 @@ where
}) => {
let mut handled_exceptions = Exceptions::empty();
for type_ in extract_handled_exceptions(handlers) {
if let Some(call_path) = self.semantic.resolve_call_path(type_) {
match call_path.segments() {
if let Some(qualified_name) = self.semantic.resolve_qualified_name(type_) {
match qualified_name.segments() {
["", "NameError"] => {
handled_exceptions |= Exceptions::NAME_ERROR;
}
Expand Down Expand Up @@ -1068,42 +1068,54 @@ where
}) => {
self.visit_expr(func);

let callable = self.semantic.resolve_call_path(func).and_then(|call_path| {
if self.semantic.match_typing_call_path(&call_path, "cast") {
Some(typing::Callable::Cast)
} else if self.semantic.match_typing_call_path(&call_path, "NewType") {
Some(typing::Callable::NewType)
} else if self.semantic.match_typing_call_path(&call_path, "TypeVar") {
Some(typing::Callable::TypeVar)
} else if self
.semantic
.match_typing_call_path(&call_path, "NamedTuple")
{
Some(typing::Callable::NamedTuple)
} else if self
.semantic
.match_typing_call_path(&call_path, "TypedDict")
{
Some(typing::Callable::TypedDict)
} else if matches!(
call_path.segments(),
[
"mypy_extensions",
"Arg"
| "DefaultArg"
| "NamedArg"
| "DefaultNamedArg"
| "VarArg"
| "KwArg"
]
) {
Some(typing::Callable::MypyExtension)
} else if matches!(call_path.segments(), ["", "bool"]) {
Some(typing::Callable::Bool)
} else {
None
}
});
let callable =
self.semantic
.resolve_qualified_name(func)
.and_then(|qualified_name| {
if self
.semantic
.match_typing_call_path(&qualified_name, "cast")
{
Some(typing::Callable::Cast)
} else if self
.semantic
.match_typing_call_path(&qualified_name, "NewType")
{
Some(typing::Callable::NewType)
} else if self
.semantic
.match_typing_call_path(&qualified_name, "TypeVar")
{
Some(typing::Callable::TypeVar)
} else if self
.semantic
.match_typing_call_path(&qualified_name, "NamedTuple")
{
Some(typing::Callable::NamedTuple)
} else if self
.semantic
.match_typing_call_path(&qualified_name, "TypedDict")
{
Some(typing::Callable::TypedDict)
} else if matches!(
qualified_name.segments(),
[
"mypy_extensions",
"Arg"
| "DefaultArg"
| "NamedArg"
| "DefaultNamedArg"
| "VarArg"
| "KwArg"
]
) {
Some(typing::Callable::MypyExtension)
} else if matches!(qualified_name.segments(), ["", "bool"]) {
Some(typing::Callable::Bool)
} else {
None
}
});
match callable {
Some(typing::Callable::Bool) => {
let mut args = arguments.args.iter();
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/renamer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ impl Renamer {
}
BindingKind::SubmoduleImport(import) => {
// Ex) Rename `import pandas.core` to `import pandas as pd`.
let module_name = import.call_path.first().unwrap();
let module_name = import.qualified_name.first().unwrap();
Some(Edit::range_replacement(
format!("{module_name} as {target}"),
binding.range(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ pub(crate) fn variable_name_task_id(
// If the function doesn't come from Airflow, we can't do anything.
if !checker
.semantic()
.resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.segments()[0], "airflow"))
.resolve_qualified_name(func)
.is_some_and(|qualified_name| matches!(qualified_name.segments()[0], "airflow"))
{
return None;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/rules/flake8_2020/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ use ruff_python_semantic::SemanticModel;

pub(super) fn is_sys(expr: &Expr, target: &str, semantic: &SemanticModel) -> bool {
semantic
.resolve_call_path(expr)
.resolve_qualified_name(expr)
.is_some_and(|call_path| call_path.segments() == ["sys", target])
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {

if checker
.semantic()
.resolve_call_path(expr)
.resolve_qualified_name(expr)
.is_some_and(|call_path| matches!(call_path.segments(), ["six", "PY3"]))
{
checker
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ruff_python_ast::ExprCall;

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::name::QualifiedName;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -41,7 +41,7 @@ impl Violation for BlockingHttpCallInAsyncFunction {
}
}

fn is_blocking_http_call(call_path: &CallPath) -> bool {
fn is_blocking_http_call(call_path: &QualifiedName) -> bool {
matches!(
call_path.segments(),
["urllib", "request", "urlopen"]
Expand All @@ -65,7 +65,7 @@ pub(crate) fn blocking_http_call(checker: &mut Checker, call: &ExprCall) {
if checker.semantic().in_async_context() {
if checker
.semantic()
.resolve_call_path(call.func.as_ref())
.resolve_qualified_name(call.func.as_ref())
.as_ref()
.is_some_and(is_blocking_http_call)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ruff_python_ast::ExprCall;

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::name::QualifiedName;
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

Expand Down Expand Up @@ -47,7 +47,7 @@ pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) {
if checker.semantic().in_async_context() {
if checker
.semantic()
.resolve_call_path(call.func.as_ref())
.resolve_qualified_name(call.func.as_ref())
.as_ref()
.is_some_and(is_unsafe_os_method)
{
Expand All @@ -60,7 +60,7 @@ pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) {
}
}

fn is_unsafe_os_method(call_path: &CallPath) -> bool {
fn is_unsafe_os_method(call_path: &QualifiedName) -> bool {
matches!(
call_path.segments(),
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,26 @@ pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::E
/// Returns `true` if the expression resolves to a blocking call, like `time.sleep` or
/// `subprocess.run`.
fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(func).is_some_and(|call_path| {
matches!(
call_path.segments(),
["", "open"]
| ["time", "sleep"]
| [
"subprocess",
"run"
| "Popen"
| "call"
| "check_call"
| "check_output"
| "getoutput"
| "getstatusoutput"
]
| ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"]
)
})
semantic
.resolve_qualified_name(func)
.is_some_and(|call_path| {
matches!(
call_path.segments(),
["", "open"]
| ["time", "sleep"]
| [
"subprocess",
"run"
| "Popen"
| "call"
| "check_call"
| "check_output"
| "getoutput"
| "getstatusoutput"
]
| ["os", "wait" | "wait3" | "wait4" | "waitid" | "waitpid"]
)
})
}

/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
Expand All @@ -94,7 +96,7 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
// Path("foo").open()
// ```
if let Expr::Call(call) = value.as_ref() {
let Some(call_path) = semantic.resolve_call_path(call.func.as_ref()) else {
let Some(call_path) = semantic.resolve_qualified_name(call.func.as_ref()) else {
return false;
};
if call_path.segments() == ["pathlib", "Path"] {
Expand Down Expand Up @@ -123,6 +125,6 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
};

semantic
.resolve_call_path(call.func.as_ref())
.resolve_qualified_name(call.func.as_ref())
.is_some_and(|call_path| call_path.segments() == ["pathlib", "Path"])
}
16 changes: 10 additions & 6 deletions crates/ruff_linter/src/rules/flake8_bandit/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@ pub(super) fn is_untyped_exception(type_: Option<&Expr>, semantic: &SemanticMode
type_.map_or(true, |type_| {
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_ {
elts.iter().any(|type_| {
semantic.resolve_call_path(type_).is_some_and(|call_path| {
matches!(call_path.segments(), ["", "Exception" | "BaseException"])
})
semantic
.resolve_qualified_name(type_)
.is_some_and(|call_path| {
matches!(call_path.segments(), ["", "Exception" | "BaseException"])
})
})
} else {
semantic.resolve_call_path(type_).is_some_and(|call_path| {
matches!(call_path.segments(), ["", "Exception" | "BaseException"])
})
semantic
.resolve_qualified_name(type_)
.is_some_and(|call_path| {
matches!(call_path.segments(), ["", "Exception" | "BaseException"])
})
}
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use anyhow::Result;

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{self as ast, Expr, Operator};
use ruff_python_semantic::{Modules, SemanticModel};
use ruff_text_size::Ranged;
Expand Down Expand Up @@ -66,7 +66,7 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)

if checker
.semantic()
.resolve_call_path(&call.func)
.resolve_qualified_name(&call.func)
.is_some_and(|call_path| matches!(call_path.segments(), ["os", "chmod"]))
{
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
Expand Down Expand Up @@ -101,7 +101,7 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
const WRITE_WORLD: u16 = 0o2;
const EXECUTE_GROUP: u16 = 0o10;

fn py_stat(call_path: &CallPath) -> Option<u16> {
fn py_stat(call_path: &QualifiedName) -> Option<u16> {
match call_path.segments() {
["stat", "ST_MODE"] => Some(0o0),
["stat", "S_IFDOOR"] => Some(0o0),
Expand Down Expand Up @@ -155,7 +155,10 @@ fn parse_mask(expr: &Expr, semantic: &SemanticModel) -> Result<Option<u16>> {
Some(value) => Ok(Some(value)),
None => anyhow::bail!("int value out of range"),
},
Expr::Attribute(_) => Ok(semantic.resolve_call_path(expr).as_ref().and_then(py_stat)),
Expr::Attribute(_) => Ok(semantic
.resolve_qualified_name(expr)
.as_ref()
.and_then(py_stat)),
Expr::BinOp(ast::ExprBinOp {
left,
op,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) {

if checker
.semantic()
.resolve_call_path(&call.func)
.resolve_qualified_name(&call.func)
.is_some_and(|call_path| {
matches!(
call_path.segments(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl Violation for ExecBuiltin {
pub(crate) fn exec_used(checker: &mut Checker, func: &Expr) {
if checker
.semantic()
.resolve_call_path(func)
.resolve_qualified_name(func)
.is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtin", "exec"]))
{
checker
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike)
{
if checker
.semantic()
.resolve_call_path(func)
.resolve_qualified_name(func)
.is_some_and(|call_path| matches!(call_path.segments(), ["tempfile", ..]))
{
return;
Expand Down
Loading

0 comments on commit bcb457d

Please sign in to comment.