CallPath newtype wrapper (#10201)

## Summary

This PR changes the `CallPath` type alias to a newtype wrapper. 

A newtype wrapper allows us to limit the API and to experiment with
alternative ways to implement matching on `CallPath`s.



## Test Plan

`cargo test`
This commit is contained in:
Micha Reiser 2024-03-03 16:54:24 +01:00 committed by GitHub
parent fb05d218c3
commit e725b6fdaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
165 changed files with 551 additions and 433 deletions

View file

@ -755,7 +755,7 @@ where
let mut handled_exceptions = Exceptions::empty(); let mut handled_exceptions = Exceptions::empty();
for type_ in extract_handled_exceptions(handlers) { for type_ in extract_handled_exceptions(handlers) {
if let Some(call_path) = self.semantic.resolve_call_path(type_) { if let Some(call_path) = self.semantic.resolve_call_path(type_) {
match call_path.as_slice() { match call_path.segments() {
["", "NameError"] => { ["", "NameError"] => {
handled_exceptions |= Exceptions::NAME_ERROR; handled_exceptions |= Exceptions::NAME_ERROR;
} }
@ -1086,7 +1086,7 @@ where
{ {
Some(typing::Callable::TypedDict) Some(typing::Callable::TypedDict)
} else if matches!( } else if matches!(
call_path.as_slice(), call_path.segments(),
[ [
"mypy_extensions", "mypy_extensions",
"Arg" "Arg"
@ -1098,7 +1098,7 @@ where
] ]
) { ) {
Some(typing::Callable::MypyExtension) Some(typing::Callable::MypyExtension)
} else if matches!(call_path.as_slice(), ["", "bool"]) { } else if matches!(call_path.segments(), ["", "bool"]) {
Some(typing::Callable::Bool) Some(typing::Callable::Bool)
} else { } else {
None None

View file

@ -69,7 +69,7 @@ pub(crate) fn variable_name_task_id(
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path[0], "airflow")) .is_some_and(|call_path| matches!(call_path.segments()[0], "airflow"))
{ {
return None; return None;
} }

View file

@ -5,5 +5,5 @@ use ruff_python_semantic::SemanticModel;
pub(super) fn is_sys(expr: &Expr, target: &str, semantic: &SemanticModel) -> bool { pub(super) fn is_sys(expr: &Expr, target: &str, semantic: &SemanticModel) -> bool {
semantic semantic
.resolve_call_path(expr) .resolve_call_path(expr)
.is_some_and(|call_path| call_path.as_slice() == ["sys", target]) .is_some_and(|call_path| call_path.segments() == ["sys", target])
} }

View file

@ -54,7 +54,7 @@ pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
if checker if checker
.semantic() .semantic()
.resolve_call_path(expr) .resolve_call_path(expr)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["six", "PY3"])) .is_some_and(|call_path| matches!(call_path.segments(), ["six", "PY3"]))
{ {
checker checker
.diagnostics .diagnostics

View file

@ -43,7 +43,7 @@ impl Violation for BlockingHttpCallInAsyncFunction {
fn is_blocking_http_call(call_path: &CallPath) -> bool { fn is_blocking_http_call(call_path: &CallPath) -> bool {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["urllib", "request", "urlopen"] ["urllib", "request", "urlopen"]
| [ | [
"httpx" | "requests", "httpx" | "requests",

View file

@ -62,7 +62,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: &CallPath) -> bool {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
[ [
"os", "os",
"popen" "popen"

View file

@ -60,7 +60,7 @@ pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, call: &ast::E
fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool { fn is_open_sleep_or_subprocess_call(func: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(func).is_some_and(|call_path| { semantic.resolve_call_path(func).is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["", "open"] ["", "open"]
| ["time", "sleep"] | ["time", "sleep"]
| [ | [
@ -97,7 +97,7 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
let Some(call_path) = semantic.resolve_call_path(call.func.as_ref()) else { let Some(call_path) = semantic.resolve_call_path(call.func.as_ref()) else {
return false; return false;
}; };
if call_path.as_slice() == ["pathlib", "Path"] { if call_path.segments() == ["pathlib", "Path"] {
return true; return true;
} }
} }
@ -124,5 +124,5 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
semantic semantic
.resolve_call_path(call.func.as_ref()) .resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| call_path.as_slice() == ["pathlib", "Path"]) .is_some_and(|call_path| call_path.segments() == ["pathlib", "Path"])
} }

View file

@ -24,12 +24,12 @@ pub(super) fn is_untyped_exception(type_: Option<&Expr>, semantic: &SemanticMode
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_ { if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_ {
elts.iter().any(|type_| { elts.iter().any(|type_| {
semantic.resolve_call_path(type_).is_some_and(|call_path| { semantic.resolve_call_path(type_).is_some_and(|call_path| {
matches!(call_path.as_slice(), ["", "Exception" | "BaseException"]) matches!(call_path.segments(), ["", "Exception" | "BaseException"])
}) })
}) })
} else { } else {
semantic.resolve_call_path(type_).is_some_and(|call_path| { semantic.resolve_call_path(type_).is_some_and(|call_path| {
matches!(call_path.as_slice(), ["", "Exception" | "BaseException"]) matches!(call_path.segments(), ["", "Exception" | "BaseException"])
}) })
} }
}) })

View file

@ -67,7 +67,7 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
if checker if checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "chmod"])) .is_some_and(|call_path| matches!(call_path.segments(), ["os", "chmod"]))
{ {
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) { if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
match parse_mask(mode_arg, checker.semantic()) { match parse_mask(mode_arg, checker.semantic()) {
@ -102,7 +102,7 @@ const WRITE_WORLD: u16 = 0o2;
const EXECUTE_GROUP: u16 = 0o10; const EXECUTE_GROUP: u16 = 0o10;
fn py_stat(call_path: &CallPath) -> Option<u16> { fn py_stat(call_path: &CallPath) -> Option<u16> {
match call_path.as_slice() { match call_path.segments() {
["stat", "ST_MODE"] => Some(0o0), ["stat", "ST_MODE"] => Some(0o0),
["stat", "S_IFDOOR"] => Some(0o0), ["stat", "S_IFDOOR"] => Some(0o0),
["stat", "S_IFPORT"] => Some(0o0), ["stat", "S_IFPORT"] => Some(0o0),

View file

@ -45,7 +45,7 @@ pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) {
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["django", "db", "models", "expressions", "RawSQL"] ["django", "db", "models", "expressions", "RawSQL"]
) )
}) })

View file

@ -36,7 +36,7 @@ pub(crate) fn exec_used(checker: &mut Checker, func: &Expr) {
if checker if checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["" | "builtin", "exec"])) .is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtin", "exec"]))
{ {
checker checker
.diagnostics .diagnostics

View file

@ -65,7 +65,7 @@ pub(crate) fn flask_debug_true(checker: &mut Checker, call: &ExprCall) {
} }
if typing::resolve_assignment(value, checker.semantic()) if typing::resolve_assignment(value, checker.semantic())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["flask", "Flask"])) .is_some_and(|call_path| matches!(call_path.segments(), ["flask", "Flask"]))
{ {
checker checker
.diagnostics .diagnostics

View file

@ -75,7 +75,7 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike)
if checker if checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["tempfile", ..])) .is_some_and(|call_path| matches!(call_path.segments(), ["tempfile", ..]))
{ {
return; return;
} }

View file

@ -65,7 +65,7 @@ pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast:
checker checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.and_then(|call_path| match call_path.as_slice() { .and_then(|call_path| match call_path.segments() {
["hashlib", "new"] => Some(HashlibCall::New), ["hashlib", "new"] => Some(HashlibCall::New),
["hashlib", "md4"] => Some(HashlibCall::WeakHash("md4")), ["hashlib", "md4"] => Some(HashlibCall::WeakHash("md4")),
["hashlib", "md5"] => Some(HashlibCall::WeakHash("md5")), ["hashlib", "md5"] => Some(HashlibCall::WeakHash("md5")),

View file

@ -62,7 +62,7 @@ pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCal
if checker if checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["jinja2", "Environment"])) .is_some_and(|call_path| matches!(call_path.segments(), ["jinja2", "Environment"]))
{ {
if let Some(keyword) = call.arguments.find_keyword("autoescape") { if let Some(keyword) = call.arguments.find_keyword("autoescape") {
match &keyword.value { match &keyword.value {

View file

@ -43,7 +43,7 @@ pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast::
if checker if checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "config", "listen"])) .is_some_and(|call_path| matches!(call_path.segments(), ["logging", "config", "listen"]))
{ {
if call.arguments.find_keyword("verify").is_some() { if call.arguments.find_keyword("verify").is_some() {
return; return;

View file

@ -48,7 +48,7 @@ pub(crate) fn mako_templates(checker: &mut Checker, call: &ast::ExprCall) {
if checker if checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["mako", "template", "Template"])) .is_some_and(|call_path| matches!(call_path.segments(), ["mako", "template", "Template"]))
{ {
checker checker
.diagnostics .diagnostics

View file

@ -40,7 +40,7 @@ pub(crate) fn paramiko_call(checker: &mut Checker, func: &Expr) {
if checker if checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["paramiko", "exec_command"])) .is_some_and(|call_path| matches!(call_path.segments(), ["paramiko", "exec_command"]))
{ {
checker checker
.diagnostics .diagnostics

View file

@ -50,7 +50,7 @@ pub(crate) fn request_with_no_cert_validation(checker: &mut Checker, call: &ast:
if let Some(target) = checker if let Some(target) = checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.and_then(|call_path| match call_path.as_slice() { .and_then(|call_path| match call_path.segments() {
["requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete"] => { ["requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete"] => {
Some("requests") Some("requests")
} }

View file

@ -55,7 +55,7 @@ pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCal
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
[ [
"requests", "requests",
"get" | "options" | "head" | "post" | "put" | "patch" | "delete" "get" | "options" | "head" | "post" | "put" | "patch" | "delete"

View file

@ -420,7 +420,7 @@ enum CallKind {
fn get_call_kind(func: &Expr, semantic: &SemanticModel) -> Option<CallKind> { fn get_call_kind(func: &Expr, semantic: &SemanticModel) -> Option<CallKind> {
semantic semantic
.resolve_call_path(func) .resolve_call_path(func)
.and_then(|call_path| match call_path.as_slice() { .and_then(|call_path| match call_path.segments() {
&[module, submodule] => match module { &[module, submodule] => match module {
"os" => match submodule { "os" => match submodule {
"execl" | "execle" | "execlp" | "execlpe" | "execv" | "execve" | "execvp" "execl" | "execle" | "execlp" | "execlpe" | "execv" | "execve" | "execvp"

View file

@ -46,7 +46,7 @@ pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall)
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["pysnmp", "hlapi", "CommunityData"]) matches!(call_path.segments(), ["pysnmp", "hlapi", "CommunityData"])
}) })
{ {
if let Some(keyword) = call.arguments.find_keyword("mpModel") { if let Some(keyword) = call.arguments.find_keyword("mpModel") {

View file

@ -47,7 +47,7 @@ pub(crate) fn snmp_weak_cryptography(checker: &mut Checker, call: &ast::ExprCall
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["pysnmp", "hlapi", "UsmUserData"]) matches!(call_path.segments(), ["pysnmp", "hlapi", "UsmUserData"])
}) })
{ {
checker checker

View file

@ -63,7 +63,7 @@ pub(crate) fn ssh_no_host_key_verification(checker: &mut Checker, call: &ExprCal
.resolve_call_path(map_callable(policy_argument)) .resolve_call_path(map_callable(policy_argument))
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["paramiko", "client", "AutoAddPolicy" | "WarningPolicy"] ["paramiko", "client", "AutoAddPolicy" | "WarningPolicy"]
| ["paramiko", "AutoAddPolicy" | "WarningPolicy"] | ["paramiko", "AutoAddPolicy" | "WarningPolicy"]
) )
@ -74,7 +74,7 @@ pub(crate) fn ssh_no_host_key_verification(checker: &mut Checker, call: &ExprCal
if typing::resolve_assignment(value, checker.semantic()).is_some_and(|call_path| { if typing::resolve_assignment(value, checker.semantic()).is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["paramiko", "client", "SSHClient"] | ["paramiko", "SSHClient"] ["paramiko", "client", "SSHClient"] | ["paramiko", "SSHClient"]
) )
}) { }) {

View file

@ -52,7 +52,7 @@ pub(crate) fn ssl_insecure_version(checker: &mut Checker, call: &ExprCall) {
let Some(keyword) = checker let Some(keyword) = checker
.semantic() .semantic()
.resolve_call_path(call.func.as_ref()) .resolve_call_path(call.func.as_ref())
.and_then(|call_path| match call_path.as_slice() { .and_then(|call_path| match call_path.segments() {
["ssl", "wrap_socket"] => Some("ssl_version"), ["ssl", "wrap_socket"] => Some("ssl_version"),
["OpenSSL", "SSL", "Context"] => Some("method"), ["OpenSSL", "SSL", "Context"] => Some("method"),
_ => None, _ => None,

View file

@ -40,7 +40,7 @@ pub(crate) fn ssl_with_no_version(checker: &mut Checker, call: &ExprCall) {
if checker if checker
.semantic() .semantic()
.resolve_call_path(call.func.as_ref()) .resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["ssl", "wrap_socket"])) .is_some_and(|call_path| matches!(call_path.segments(), ["ssl", "wrap_socket"]))
{ {
if call.arguments.find_keyword("ssl_version").is_none() { if call.arguments.find_keyword("ssl_version").is_none() {
checker checker

View file

@ -826,7 +826,7 @@ impl Violation for SuspiciousFTPLibUsage {
/// S301, S302, S303, S304, S305, S306, S307, S308, S310, S311, S312, S313, S314, S315, S316, S317, S318, S319, S320, S321, S323 /// S301, S302, S303, S304, S305, S306, S307, S308, S310, S311, S312, S313, S314, S315, S316, S317, S318, S319, S320, S321, S323
pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) { pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
let Some(diagnostic_kind) = checker.semantic().resolve_call_path(call.func.as_ref()).and_then(|call_path| { let Some(diagnostic_kind) = checker.semantic().resolve_call_path(call.func.as_ref()).and_then(|call_path| {
match call_path.as_slice() { match call_path.segments() {
// Pickle // Pickle
["pickle" | "dill", "load" | "loads" | "Unpickler"] | ["pickle" | "dill", "load" | "loads" | "Unpickler"] |
["shelve", "open" | "DbfilenameShelf"] | ["shelve", "open" | "DbfilenameShelf"] |
@ -908,7 +908,7 @@ pub(crate) fn suspicious_function_decorator(checker: &mut Checker, decorator: &D
.semantic() .semantic()
.resolve_call_path(&decorator.expression) .resolve_call_path(&decorator.expression)
.and_then(|call_path| { .and_then(|call_path| {
match call_path.as_slice() { match call_path.segments() {
// MarkSafe // MarkSafe
["django", "utils", "safestring" | "html", "mark_safe"] => { ["django", "utils", "safestring" | "html", "mark_safe"] => {
Some(SuspiciousMarkSafeUsage.into()) Some(SuspiciousMarkSafeUsage.into())

View file

@ -63,7 +63,7 @@ pub(crate) fn unsafe_yaml_load(checker: &mut Checker, call: &ast::ExprCall) {
if checker if checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["yaml", "load"])) .is_some_and(|call_path| matches!(call_path.segments(), ["yaml", "load"]))
{ {
if let Some(loader_arg) = call.arguments.find_argument("Loader", 1) { if let Some(loader_arg) = call.arguments.find_argument("Loader", 1) {
if !checker if !checker
@ -71,7 +71,7 @@ pub(crate) fn unsafe_yaml_load(checker: &mut Checker, call: &ast::ExprCall) {
.resolve_call_path(loader_arg) .resolve_call_path(loader_arg)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["yaml", "SafeLoader" | "CSafeLoader"] ["yaml", "SafeLoader" | "CSafeLoader"]
| ["yaml", "loader", "SafeLoader" | "CSafeLoader"] | ["yaml", "loader", "SafeLoader" | "CSafeLoader"]
) )

View file

@ -102,7 +102,7 @@ fn extract_cryptographic_key(
call: &ExprCall, call: &ExprCall,
) -> Option<(CryptographicKey, TextRange)> { ) -> Option<(CryptographicKey, TextRange)> {
let call_path = checker.semantic().resolve_call_path(&call.func)?; let call_path = checker.semantic().resolve_call_path(&call.func)?;
match call_path.as_slice() { match call_path.segments() {
["cryptography", "hazmat", "primitives", "asymmetric", function, "generate_private_key"] => { ["cryptography", "hazmat", "primitives", "asymmetric", function, "generate_private_key"] => {
match *function { match *function {
"dsa" => { "dsa" => {
@ -118,7 +118,7 @@ fn extract_cryptographic_key(
let ExprAttribute { attr, value, .. } = argument.as_attribute_expr()?; let ExprAttribute { attr, value, .. } = argument.as_attribute_expr()?;
let call_path = checker.semantic().resolve_call_path(value)?; let call_path = checker.semantic().resolve_call_path(value)?;
if matches!( if matches!(
call_path.as_slice(), call_path.segments(),
["cryptography", "hazmat", "primitives", "asymmetric", "ec"] ["cryptography", "hazmat", "primitives", "asymmetric", "ec"]
) { ) {
Some(( Some((

View file

@ -141,7 +141,7 @@ pub(crate) fn blind_except(
if checker if checker
.semantic() .semantic()
.resolve_call_path(func.as_ref()) .resolve_call_path(func.as_ref())
.is_some_and(|call_path| match call_path.as_slice() { .is_some_and(|call_path| match call_path.segments() {
["logging", "exception"] => true, ["logging", "exception"] => true,
["logging", "error"] => { ["logging", "error"] => {
if let Some(keyword) = arguments.find_keyword("exc_info") { if let Some(keyword) = arguments.find_keyword("exc_info") {

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters}; use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters};
use ruff_python_semantic::analyze::visibility; use ruff_python_semantic::analyze::visibility;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
@ -119,8 +119,8 @@ pub(crate) fn boolean_default_value_positional_argument(
{ {
// Allow Boolean defaults in setters. // Allow Boolean defaults in setters.
if decorator_list.iter().any(|decorator| { if decorator_list.iter().any(|decorator| {
collect_call_path(&decorator.expression) CallPath::from_expr(&decorator.expression)
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"]) .is_some_and(|call_path| call_path.segments() == [name, "setter"])
}) { }) {
return; return;
} }

View file

@ -3,7 +3,7 @@ use ruff_python_ast::{self as ast, Decorator, Expr, ParameterWithDefault, Parame
use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Violation; use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::call_path::CallPath;
use ruff_python_semantic::analyze::visibility; use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::SemanticModel; use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
@ -136,8 +136,8 @@ pub(crate) fn boolean_type_hint_positional_argument(
// Allow Boolean type hints in setters. // Allow Boolean type hints in setters.
if decorator_list.iter().any(|decorator| { if decorator_list.iter().any(|decorator| {
collect_call_path(&decorator.expression) CallPath::from_expr(&decorator.expression)
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"]) .is_some_and(|call_path| call_path.segments() == [name, "setter"])
}) { }) {
return; return;
} }

View file

@ -110,11 +110,11 @@ fn is_abc_class(bases: &[Expr], keywords: &[Keyword], semantic: &SemanticModel)
keyword.arg.as_ref().is_some_and(|arg| arg == "metaclass") keyword.arg.as_ref().is_some_and(|arg| arg == "metaclass")
&& semantic && semantic
.resolve_call_path(&keyword.value) .resolve_call_path(&keyword.value)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["abc", "ABCMeta"])) .is_some_and(|call_path| matches!(call_path.segments(), ["abc", "ABCMeta"]))
}) || bases.iter().any(|base| { }) || bases.iter().any(|base| {
semantic semantic
.resolve_call_path(base) .resolve_call_path(base)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["abc", "ABC"])) .is_some_and(|call_path| matches!(call_path.segments(), ["abc", "ABC"]))
}) })
} }

View file

@ -98,7 +98,7 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
let Some(exception) = checker let Some(exception) = checker
.semantic() .semantic()
.resolve_call_path(arg) .resolve_call_path(arg)
.and_then(|call_path| match call_path.as_slice() { .and_then(|call_path| match call_path.segments() {
["", "Exception"] => Some(ExceptionKind::Exception), ["", "Exception"] => Some(ExceptionKind::Exception),
["", "BaseException"] => Some(ExceptionKind::BaseException), ["", "BaseException"] => Some(ExceptionKind::BaseException),
_ => None, _ => None,
@ -113,7 +113,7 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
} else if checker } else if checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pytest", "raises"])) .is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "raises"]))
&& arguments.find_keyword("match").is_none() && arguments.find_keyword("match").is_none()
{ {
AssertionKind::PytestRaises AssertionKind::PytestRaises

View file

@ -72,7 +72,7 @@ impl Violation for CachedInstanceMethod {
fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool { fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(expr).is_some_and(|call_path| { semantic.resolve_call_path(expr).is_some_and(|call_path| {
matches!(call_path.as_slice(), ["functools", "lru_cache" | "cache"]) matches!(call_path.segments(), ["functools", "lru_cache" | "cache"])
}) })
} }

View file

@ -6,7 +6,6 @@ use rustc_hash::{FxHashMap, FxHashSet};
use ruff_diagnostics::{AlwaysFixableViolation, Violation}; use ruff_diagnostics::{AlwaysFixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path;
use ruff_python_ast::call_path::CallPath; use ruff_python_ast::call_path::CallPath;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -124,7 +123,7 @@ fn duplicate_handler_exceptions<'a>(
let mut duplicates: FxHashSet<CallPath> = FxHashSet::default(); let mut duplicates: FxHashSet<CallPath> = FxHashSet::default();
let mut unique_elts: Vec<&Expr> = Vec::default(); let mut unique_elts: Vec<&Expr> = Vec::default();
for type_ in elts { for type_ in elts {
if let Some(call_path) = call_path::collect_call_path(type_) { if let Some(call_path) = CallPath::from_expr(type_) {
if seen.contains_key(&call_path) { if seen.contains_key(&call_path) {
duplicates.insert(call_path); duplicates.insert(call_path);
} else { } else {
@ -141,7 +140,7 @@ fn duplicate_handler_exceptions<'a>(
DuplicateHandlerException { DuplicateHandlerException {
names: duplicates names: duplicates
.into_iter() .into_iter()
.map(|call_path| call_path.join(".")) .map(|call_path| call_path.segments().join("."))
.sorted() .sorted()
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
}, },
@ -184,7 +183,7 @@ pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHand
}; };
match type_.as_ref() { match type_.as_ref() {
Expr::Attribute(_) | Expr::Name(_) => { Expr::Attribute(_) | Expr::Name(_) => {
if let Some(call_path) = call_path::collect_call_path(type_) { if let Some(call_path) = CallPath::from_expr(type_) {
if seen.contains(&call_path) { if seen.contains(&call_path) {
duplicates.entry(call_path).or_default().push(type_); duplicates.entry(call_path).or_default().push(type_);
} else { } else {
@ -210,7 +209,7 @@ pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHand
for expr in exprs { for expr in exprs {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
DuplicateTryBlockException { DuplicateTryBlockException {
name: name.join("."), name: name.segments().join("."),
}, },
expr.range(), expr.range(),
)); ));

View file

@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange};
use ruff_diagnostics::Violation; use ruff_diagnostics::Violation;
use ruff_diagnostics::{Diagnostic, DiagnosticKind}; use ruff_diagnostics::{Diagnostic, DiagnosticKind};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::{compose_call_path, from_qualified_name, CallPath}; use ruff_python_ast::call_path::{compose_call_path, CallPath};
use ruff_python_ast::visitor; use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
use ruff_python_semantic::analyze::typing::{ use ruff_python_semantic::analyze::typing::{
@ -128,7 +128,7 @@ pub(crate) fn function_call_in_argument_default(checker: &mut Checker, parameter
.flake8_bugbear .flake8_bugbear
.extend_immutable_calls .extend_immutable_calls
.iter() .iter()
.map(|target| from_qualified_name(target)) .map(|target| CallPath::from_qualified_name(target))
.collect(); .collect();
let mut visitor = ArgumentDefaultVisitor::new(checker.semantic(), &extend_immutable_calls); let mut visitor = ArgumentDefaultVisitor::new(checker.semantic(), &extend_immutable_calls);

View file

@ -1,4 +1,4 @@
use ast::call_path::{from_qualified_name, CallPath}; use ast::call_path::CallPath;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_ast::helpers::is_docstring_stmt;
@ -103,7 +103,7 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, function_def: &ast
.flake8_bugbear .flake8_bugbear
.extend_immutable_calls .extend_immutable_calls
.iter() .iter()
.map(|target| from_qualified_name(target)) .map(|target| CallPath::from_qualified_name(target))
.collect(); .collect();
if is_mutable_expr(default, checker.semantic()) if is_mutable_expr(default, checker.semantic())

View file

@ -41,7 +41,7 @@ pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, call: &ast::ExprCall
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["warnings", "warn"])) .is_some_and(|call_path| matches!(call_path.segments(), ["warnings", "warn"]))
{ {
return; return;
} }

View file

@ -64,7 +64,7 @@ pub(crate) fn re_sub_positional_args(checker: &mut Checker, call: &ast::ExprCall
let Some(method) = checker let Some(method) = checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.and_then(|call_path| match call_path.as_slice() { .and_then(|call_path| match call_path.segments() {
["re", "sub"] => Some(Method::Sub), ["re", "sub"] => Some(Method::Sub),
["re", "subn"] => Some(Method::Subn), ["re", "subn"] => Some(Method::Subn),
["re", "split"] => Some(Method::Split), ["re", "split"] => Some(Method::Split),

View file

@ -314,7 +314,7 @@ pub(crate) fn reuse_of_groupby_generator(
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["itertools", "groupby"])) .is_some_and(|call_path| matches!(call_path.segments(), ["itertools", "groupby"]))
{ {
return; return;
} }

View file

@ -60,7 +60,7 @@ pub(crate) fn useless_contextlib_suppress(
&& checker && checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["contextlib", "suppress"])) .is_some_and(|call_path| matches!(call_path.segments(), ["contextlib", "suppress"]))
{ {
checker checker
.diagnostics .diagnostics

View file

@ -100,7 +100,7 @@ fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
}; };
semantic.resolve_call_path(func).is_some_and(|call_path| { semantic.resolve_call_path(func).is_some_and(|call_path| {
match call_path.as_slice() { match call_path.segments() {
["itertools", "cycle" | "count"] => true, ["itertools", "cycle" | "count"] => true,
["itertools", "repeat"] => { ["itertools", "repeat"] => {
// Ex) `itertools.repeat(1)` // Ex) `itertools.repeat(1)`

View file

@ -66,7 +66,7 @@ pub(crate) fn call_date_fromtimestamp(checker: &mut Checker, func: &Expr, locati
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["datetime", "date", "fromtimestamp"]) matches!(call_path.segments(), ["datetime", "date", "fromtimestamp"])
}) })
{ {
checker checker

View file

@ -64,7 +64,7 @@ pub(crate) fn call_date_today(checker: &mut Checker, func: &Expr, location: Text
if checker if checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["datetime", "date", "today"])) .is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "date", "today"]))
{ {
checker checker
.diagnostics .diagnostics

View file

@ -70,7 +70,7 @@ pub(crate) fn call_datetime_fromtimestamp(checker: &mut Checker, call: &ast::Exp
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["datetime", "datetime", "fromtimestamp"] ["datetime", "datetime", "fromtimestamp"]
) )
}) })

View file

@ -64,7 +64,7 @@ pub(crate) fn call_datetime_now_without_tzinfo(checker: &mut Checker, call: &ast
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["datetime", "datetime", "now"])) .is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "datetime", "now"]))
{ {
return; return;
} }

View file

@ -73,7 +73,7 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["datetime", "datetime", "strptime"]) matches!(call_path.segments(), ["datetime", "datetime", "strptime"])
}) })
{ {
return; return;

View file

@ -63,7 +63,7 @@ pub(crate) fn call_datetime_today(checker: &mut Checker, func: &Expr, location:
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["datetime", "datetime", "today"])) .is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "datetime", "today"]))
{ {
return; return;
} }

View file

@ -73,7 +73,7 @@ pub(crate) fn call_datetime_utcfromtimestamp(
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["datetime", "datetime", "utcfromtimestamp"] ["datetime", "datetime", "utcfromtimestamp"]
) )
}) })

View file

@ -67,7 +67,7 @@ pub(crate) fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location:
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["datetime", "datetime", "utcnow"])) .is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "datetime", "utcnow"]))
{ {
return; return;
} }

View file

@ -60,7 +60,7 @@ pub(crate) fn call_datetime_without_tzinfo(checker: &mut Checker, call: &ast::Ex
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["datetime", "datetime"])) .is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "datetime"]))
{ {
return; return;
} }

View file

@ -2,7 +2,7 @@ use ruff_python_ast::{Expr, Stmt};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::{format_call_path, from_unqualified_name, CallPath}; use ruff_python_ast::call_path::{CallPath, CallPathBuilder};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -53,7 +53,7 @@ pub(crate) fn debugger_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
.resolve_call_path(func) .resolve_call_path(func)
.and_then(|call_path| { .and_then(|call_path| {
if is_debugger_call(&call_path) { if is_debugger_call(&call_path) {
Some(DebuggerUsingType::Call(format_call_path(&call_path))) Some(DebuggerUsingType::Call(call_path.to_string()))
} else { } else {
None None
} }
@ -68,19 +68,20 @@ pub(crate) fn debugger_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
/// Checks for the presence of a debugger import. /// Checks for the presence of a debugger import.
pub(crate) fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option<Diagnostic> { pub(crate) fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option<Diagnostic> {
if let Some(module) = module { if let Some(module) = module {
let mut call_path: CallPath = from_unqualified_name(module); let mut builder = CallPathBuilder::from_path(CallPath::from_unqualified_name(module));
call_path.push(name); builder.push(name);
let call_path = builder.build();
if is_debugger_call(&call_path) { if is_debugger_call(&call_path) {
return Some(Diagnostic::new( return Some(Diagnostic::new(
Debugger { Debugger {
using_type: DebuggerUsingType::Import(format_call_path(&call_path)), using_type: DebuggerUsingType::Import(call_path.to_string()),
}, },
stmt.range(), stmt.range(),
)); ));
} }
} else { } else {
let call_path: CallPath = from_unqualified_name(name); let call_path: CallPath = CallPath::from_unqualified_name(name);
if is_debugger_import(&call_path) { if is_debugger_import(&call_path) {
return Some(Diagnostic::new( return Some(Diagnostic::new(
@ -96,7 +97,7 @@ pub(crate) fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) ->
fn is_debugger_call(call_path: &CallPath) -> bool { fn is_debugger_call(call_path: &CallPath) -> bool {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["pdb" | "pudb" | "ipdb", "set_trace"] ["pdb" | "pudb" | "ipdb", "set_trace"]
| ["ipdb", "sset_trace"] | ["ipdb", "sset_trace"]
| ["IPython", "terminal", "embed", "InteractiveShellEmbed"] | ["IPython", "terminal", "embed", "InteractiveShellEmbed"]
@ -120,7 +121,7 @@ fn is_debugger_import(call_path: &CallPath) -> bool {
// As a special-case, we omit `builtins` to allow `import builtins`, which is far more general // As a special-case, we omit `builtins` to allow `import builtins`, which is far more general
// than (e.g.) `import celery.contrib.rdb`. // than (e.g.) `import celery.contrib.rdb`.
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["pdb" | "pudb" | "ipdb" | "debugpy" | "ptvsd"] ["pdb" | "pudb" | "ipdb" | "debugpy" | "ptvsd"]
| ["IPython", "terminal", "embed"] | ["IPython", "terminal", "embed"]
| ["IPython", "frontend", "terminal", "embed",] | ["IPython", "frontend", "terminal", "embed",]

View file

@ -5,7 +5,7 @@ use ruff_python_semantic::{analyze, SemanticModel};
/// Return `true` if a Python class appears to be a Django model, based on its base classes. /// Return `true` if a Python class appears to be a Django model, based on its base classes.
pub(super) fn is_model(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { pub(super) fn is_model(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
analyze::class::any_call_path(class_def, semantic, &|call_path| { analyze::class::any_call_path(class_def, semantic, &|call_path| {
matches!(call_path.as_slice(), ["django", "db", "models", "Model"]) matches!(call_path.segments(), ["django", "db", "models", "Model"])
}) })
} }
@ -13,7 +13,7 @@ pub(super) fn is_model(class_def: &ast::StmtClassDef, semantic: &SemanticModel)
pub(super) fn is_model_form(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { pub(super) fn is_model_form(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
analyze::class::any_call_path(class_def, semantic, &|call_path| { analyze::class::any_call_path(class_def, semantic, &|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["django", "forms", "ModelForm"] | ["django", "forms", "models", "ModelForm"] ["django", "forms", "ModelForm"] | ["django", "forms", "models", "ModelForm"]
) )
}) })
@ -23,7 +23,7 @@ pub(super) fn is_model_form(class_def: &ast::StmtClassDef, semantic: &SemanticMo
pub(super) fn is_model_field(expr: &Expr, semantic: &SemanticModel) -> bool { pub(super) fn is_model_field(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(expr).is_some_and(|call_path| { semantic.resolve_call_path(expr).is_some_and(|call_path| {
call_path call_path
.as_slice() .segments()
.starts_with(&["django", "db", "models"]) .starts_with(&["django", "db", "models"])
}) })
} }
@ -34,7 +34,7 @@ pub(super) fn get_model_field_name<'a>(
semantic: &'a SemanticModel, semantic: &'a SemanticModel,
) -> Option<&'a str> { ) -> Option<&'a str> {
semantic.resolve_call_path(expr).and_then(|call_path| { semantic.resolve_call_path(expr).and_then(|call_path| {
let call_path = call_path.as_slice(); let call_path = call_path.segments();
if !call_path.starts_with(&["django", "db", "models"]) { if !call_path.starts_with(&["django", "db", "models"]) {
return None; return None;
} }

View file

@ -52,7 +52,7 @@ pub(crate) fn locals_in_render_function(checker: &mut Checker, call: &ast::ExprC
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["django", "shortcuts", "render"])) .is_some_and(|call_path| matches!(call_path.segments(), ["django", "shortcuts", "render"]))
{ {
return; return;
} }
@ -73,5 +73,5 @@ fn is_locals_call(expr: &Expr, semantic: &SemanticModel) -> bool {
}; };
semantic semantic
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["", "locals"])) .is_some_and(|call_path| matches!(call_path.segments(), ["", "locals"]))
} }

View file

@ -63,7 +63,7 @@ pub(crate) fn non_leading_receiver_decorator(checker: &mut Checker, decorator_li
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["django", "dispatch", "receiver"]) matches!(call_path.segments(), ["django", "dispatch", "receiver"])
}) })
}); });
if i > 0 && is_receiver && !seen_receiver { if i > 0 && is_receiver && !seen_receiver {

View file

@ -2,7 +2,6 @@ use ruff_python_ast::Expr;
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::format_call_path;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -82,7 +81,7 @@ pub(crate) fn future_rewritable_type_annotation(checker: &mut Checker, expr: &Ex
let name = checker let name = checker
.semantic() .semantic()
.resolve_call_path(expr) .resolve_call_path(expr)
.map(|binding| format_call_path(&binding)); .map(|binding| binding.to_string());
if let Some(name) = name { if let Some(name) = name {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -62,7 +62,7 @@ pub(crate) fn direct_logger_instantiation(checker: &mut Checker, call: &ast::Exp
if checker if checker
.semantic() .semantic()
.resolve_call_path(call.func.as_ref()) .resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "Logger"])) .is_some_and(|call_path| matches!(call_path.segments(), ["logging", "Logger"]))
{ {
let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range()); let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range());
diagnostic.try_set_fix(|| { diagnostic.try_set_fix(|| {

View file

@ -63,7 +63,7 @@ pub(crate) fn exception_without_exc_info(checker: &mut Checker, call: &ExprCall)
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(call.func.as_ref()) .resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "exception"])) .is_some_and(|call_path| matches!(call_path.segments(), ["logging", "exception"]))
{ {
return; return;
} }

View file

@ -78,7 +78,7 @@ pub(crate) fn invalid_get_logger_argument(checker: &mut Checker, call: &ast::Exp
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(call.func.as_ref()) .resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "getLogger"])) .is_some_and(|call_path| matches!(call_path.segments(), ["logging", "getLogger"]))
{ {
return; return;
} }

View file

@ -56,7 +56,7 @@ pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) {
if checker if checker
.semantic() .semantic()
.resolve_call_path(expr) .resolve_call_path(expr)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "WARN"])) .is_some_and(|call_path| matches!(call_path.segments(), ["logging", "WARN"]))
{ {
let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range()); let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range());
diagnostic.try_set_fix(|| { diagnostic.try_set_fix(|| {

View file

@ -111,7 +111,7 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
if checker if checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["", "dict"])) .is_some_and(|call_path| matches!(call_path.segments(), ["", "dict"]))
{ {
for keyword in keywords.iter() { for keyword in keywords.iter() {
if let Some(attr) = &keyword.arg { if let Some(attr) = &keyword.arg {
@ -168,7 +168,7 @@ pub(crate) fn logging_call(checker: &mut Checker, call: &ast::ExprCall) {
let Some(call_path) = checker.semantic().resolve_call_path(call.func.as_ref()) else { let Some(call_path) = checker.semantic().resolve_call_path(call.func.as_ref()) else {
return; return;
}; };
let ["logging", attribute] = call_path.as_slice() else { let ["logging", attribute] = call_path.segments() else {
return; return;
}; };
let Some(call_type) = LoggingCallType::from_attribute(attribute) else { let Some(call_type) = LoggingCallType::from_attribute(attribute) else {

View file

@ -63,7 +63,7 @@ pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stm
checker checker
.semantic() .semantic()
.resolve_call_path(expr) .resolve_call_path(expr)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["enum", "Enum"])) .is_some_and(|call_path| matches!(call_path.segments(), ["enum", "Enum"]))
}) { }) {
return; return;
} }
@ -78,7 +78,7 @@ pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stm
if checker if checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["enum", "auto"])) .is_some_and(|call_path| matches!(call_path.segments(), ["enum", "auto"]))
{ {
continue; continue;
} }

View file

@ -101,7 +101,7 @@ pub(crate) fn print_call(checker: &mut Checker, call: &ast::ExprCall) {
let call_path = checker.semantic().resolve_call_path(&call.func); let call_path = checker.semantic().resolve_call_path(&call.func);
if call_path if call_path
.as_ref() .as_ref()
.is_some_and(|call_path| matches!(call_path.as_slice(), ["", "print"])) .is_some_and(|call_path| matches!(call_path.segments(), ["", "print"]))
{ {
// If the print call has a `file=` argument (that isn't `None`, `"sys.stdout"`, // If the print call has a `file=` argument (that isn't `None`, `"sys.stdout"`,
// or `"sys.stderr"`), don't trigger T201. // or `"sys.stderr"`), don't trigger T201.
@ -110,8 +110,8 @@ pub(crate) fn print_call(checker: &mut Checker, call: &ast::ExprCall) {
if checker.semantic().resolve_call_path(&keyword.value).map_or( if checker.semantic().resolve_call_path(&keyword.value).map_or(
true, true,
|call_path| { |call_path| {
call_path.as_slice() != ["sys", "stdout"] call_path.segments() != ["sys", "stdout"]
&& call_path.as_slice() != ["sys", "stderr"] && call_path.segments() != ["sys", "stderr"]
}, },
) { ) {
return; return;
@ -121,7 +121,7 @@ pub(crate) fn print_call(checker: &mut Checker, call: &ast::ExprCall) {
Diagnostic::new(Print, call.func.range()) Diagnostic::new(Print, call.func.range())
} else if call_path } else if call_path
.as_ref() .as_ref()
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pprint", "pprint"])) .is_some_and(|call_path| matches!(call_path.segments(), ["pprint", "pprint"]))
{ {
Diagnostic::new(PPrint, call.func.range()) Diagnostic::new(PPrint, call.func.range())
} else { } else {

View file

@ -128,7 +128,7 @@ pub(crate) fn bad_generator_return_type(
let Some(call_path) = semantic.resolve_call_path(map_subscript(returns)) else { let Some(call_path) = semantic.resolve_call_path(map_subscript(returns)) else {
return; return;
}; };
match (name, call_path.as_slice()) { match (name, call_path.segments()) {
("__iter__", ["typing", "Generator"]) => { ("__iter__", ["typing", "Generator"]) => {
(Method::Iter, Module::Typing, Generator::Generator) (Method::Iter, Module::Typing, Generator::Generator)
} }

View file

@ -76,7 +76,7 @@ pub(crate) fn bad_version_info_comparison(checker: &mut Checker, test: &Expr) {
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(left) .resolve_call_path(left)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["sys", "version_info"])) .is_some_and(|call_path| matches!(call_path.segments(), ["sys", "version_info"]))
{ {
return; return;
} }

View file

@ -58,7 +58,7 @@ pub(crate) fn collections_named_tuple(checker: &mut Checker, expr: &Expr) {
if checker if checker
.semantic() .semantic()
.resolve_call_path(expr) .resolve_call_path(expr)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["collections", "namedtuple"])) .is_some_and(|call_path| matches!(call_path.segments(), ["collections", "namedtuple"]))
{ {
checker checker
.diagnostics .diagnostics

View file

@ -69,7 +69,7 @@ pub(crate) fn complex_if_statement_in_stub(checker: &mut Checker, test: &Expr) {
.semantic() .semantic()
.resolve_call_path(left) .resolve_call_path(left)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["sys", "version_info" | "platform"]) matches!(call_path.segments(), ["sys", "version_info" | "platform"])
}) })
{ {
return; return;

View file

@ -309,7 +309,7 @@ fn is_object_or_unused(expr: &Expr, semantic: &SemanticModel) -> bool {
.as_ref() .as_ref()
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["" | "builtins", "object"] | ["_typeshed", "Unused"] ["" | "builtins", "object"] | ["_typeshed", "Unused"]
) )
}) })
@ -320,7 +320,7 @@ fn is_base_exception(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic semantic
.resolve_call_path(expr) .resolve_call_path(expr)
.as_ref() .as_ref()
.is_some_and(|call_path| matches!(call_path.as_slice(), ["" | "builtins", "BaseException"])) .is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtins", "BaseException"]))
} }
/// Return `true` if the [`Expr`] is the `types.TracebackType` type. /// Return `true` if the [`Expr`] is the `types.TracebackType` type.
@ -328,7 +328,7 @@ fn is_traceback_type(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic semantic
.resolve_call_path(expr) .resolve_call_path(expr)
.as_ref() .as_ref()
.is_some_and(|call_path| matches!(call_path.as_slice(), ["types", "TracebackType"])) .is_some_and(|call_path| matches!(call_path.segments(), ["types", "TracebackType"]))
} }
/// Return `true` if the [`Expr`] is, e.g., `Type[BaseException]`. /// Return `true` if the [`Expr`] is, e.g., `Type[BaseException]`.
@ -341,7 +341,7 @@ fn is_base_exception_type(expr: &Expr, semantic: &SemanticModel) -> bool {
|| semantic || semantic
.resolve_call_path(value) .resolve_call_path(value)
.as_ref() .as_ref()
.is_some_and(|call_path| matches!(call_path.as_slice(), ["" | "builtins", "type"])) .is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtins", "type"]))
{ {
is_base_exception(slice, semantic) is_base_exception(slice, semantic)
} else { } else {

View file

@ -92,12 +92,12 @@ pub(crate) fn iter_method_return_iterable(checker: &mut Checker, definition: &De
.is_some_and(|call_path| { .is_some_and(|call_path| {
if is_async { if is_async {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["typing", "AsyncIterable"] | ["collections", "abc", "AsyncIterable"] ["typing", "AsyncIterable"] | ["collections", "abc", "AsyncIterable"]
) )
} else { } else {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["typing", "Iterable"] | ["collections", "abc", "Iterable"] ["typing", "Iterable"] | ["collections", "abc", "Iterable"]
) )
} }

View file

@ -233,7 +233,7 @@ fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool
fn is_metaclass_base(base: &Expr, semantic: &SemanticModel) -> bool { fn is_metaclass_base(base: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(base).is_some_and(|call_path| { semantic.resolve_call_path(base).is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"] ["" | "builtins", "type"] | ["abc", "ABCMeta"] | ["enum", "EnumMeta" | "EnumType"]
) )
}) })
@ -282,7 +282,7 @@ fn is_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool
.resolve_call_path(map_subscript(expr)) .resolve_call_path(map_subscript(expr))
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["typing", "Iterator"] | ["collections", "abc", "Iterator"] ["typing", "Iterator"] | ["collections", "abc", "Iterator"]
) )
}) })
@ -295,7 +295,7 @@ fn is_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
.resolve_call_path(map_subscript(expr)) .resolve_call_path(map_subscript(expr))
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["typing", "Iterable" | "Iterator"] ["typing", "Iterable" | "Iterator"]
| ["collections", "abc", "Iterable" | "Iterator"] | ["collections", "abc", "Iterable" | "Iterator"]
) )
@ -312,7 +312,7 @@ fn is_async_iterator(arguments: Option<&Arguments>, semantic: &SemanticModel) ->
.resolve_call_path(map_subscript(expr)) .resolve_call_path(map_subscript(expr))
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["typing", "AsyncIterator"] | ["collections", "abc", "AsyncIterator"] ["typing", "AsyncIterator"] | ["collections", "abc", "AsyncIterator"]
) )
}) })
@ -325,7 +325,7 @@ fn is_async_iterable(expr: &Expr, semantic: &SemanticModel) -> bool {
.resolve_call_path(map_subscript(expr)) .resolve_call_path(map_subscript(expr))
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["typing", "AsyncIterable" | "AsyncIterator"] ["typing", "AsyncIterable" | "AsyncIterator"]
| ["collections", "abc", "AsyncIterable" | "AsyncIterator"] | ["collections", "abc", "AsyncIterable" | "AsyncIterator"]
) )

View file

@ -94,7 +94,7 @@ fn check_annotation(checker: &mut Checker, annotation: &Expr) {
return; return;
}; };
match call_path.as_slice() { match call_path.segments() {
["" | "builtins", "int"] => has_int = true, ["" | "builtins", "int"] => has_int = true,
["" | "builtins", "float"] => has_float = true, ["" | "builtins", "float"] => has_float = true,
["" | "builtins", "complex"] => has_complex = true, ["" | "builtins", "complex"] => has_complex = true,

View file

@ -249,12 +249,12 @@ impl AlwaysFixableViolation for TypeAliasWithoutAnnotation {
} }
fn is_allowed_negated_math_attribute(call_path: &CallPath) -> bool { fn is_allowed_negated_math_attribute(call_path: &CallPath) -> bool {
matches!(call_path.as_slice(), ["math", "inf" | "e" | "pi" | "tau"]) matches!(call_path.segments(), ["math", "inf" | "e" | "pi" | "tau"])
} }
fn is_allowed_math_attribute(call_path: &CallPath) -> bool { fn is_allowed_math_attribute(call_path: &CallPath) -> bool {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["math", "inf" | "nan" | "e" | "pi" | "tau"] ["math", "inf" | "nan" | "e" | "pi" | "tau"]
| [ | [
"sys", "sys",
@ -437,7 +437,7 @@ fn is_type_var_like_call(expr: &Expr, semantic: &SemanticModel) -> bool {
}; };
semantic.resolve_call_path(func).is_some_and(|call_path| { semantic.resolve_call_path(func).is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
[ [
"typing" | "typing_extensions", "typing" | "typing_extensions",
"TypeVar" | "TypeVarTuple" | "NewType" | "ParamSpec" "TypeVar" | "TypeVarTuple" | "NewType" | "ParamSpec"
@ -483,7 +483,7 @@ fn is_enum(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
.resolve_call_path(map_subscript(expr)) .resolve_call_path(map_subscript(expr))
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
[ [
"enum", "enum",
"Enum" | "Flag" | "IntEnum" | "IntFlag" | "StrEnum" | "ReprEnum" "Enum" | "Flag" | "IntEnum" | "IntFlag" | "StrEnum" | "ReprEnum"

View file

@ -82,7 +82,7 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
.semantic() .semantic()
.resolve_call_path(returns) .resolve_call_path(returns)
.map_or(true, |call_path| { .map_or(true, |call_path| {
!matches!(call_path.as_slice(), ["" | "builtins", "str"]) !matches!(call_path.segments(), ["" | "builtins", "str"])
}) })
{ {
return; return;

View file

@ -87,7 +87,7 @@ fn is_warnings_dot_deprecated(expr: Option<&ast::Expr>, semantic: &SemanticModel
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["warnings" | "typing_extensions", "deprecated"] ["warnings" | "typing_extensions", "deprecated"]
) )
}) })

View file

@ -81,7 +81,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr)
if checker if checker
.semantic() .semantic()
.resolve_call_path(unwrapped.value.as_ref()) .resolve_call_path(unwrapped.value.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["" | "builtins", "type"])) .is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtins", "type"]))
{ {
type_exprs.push(unwrapped.slice.as_ref()); type_exprs.push(unwrapped.slice.as_ref());
} else { } else {

View file

@ -108,7 +108,7 @@ pub(crate) fn unrecognized_platform(checker: &mut Checker, test: &Expr) {
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(left) .resolve_call_path(left)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["sys", "platform"])) .is_some_and(|call_path| matches!(call_path.segments(), ["sys", "platform"]))
{ {
return; return;
} }

View file

@ -136,7 +136,7 @@ pub(crate) fn unrecognized_version_info(checker: &mut Checker, test: &Expr) {
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(map_subscript(left)) .resolve_call_path(map_subscript(left))
.is_some_and(|call_path| matches!(call_path.as_slice(), ["sys", "version_info"])) .is_some_and(|call_path| matches!(call_path.segments(), ["sys", "version_info"]))
{ {
return; return;
} }

View file

@ -3,7 +3,7 @@ use std::fmt;
use ruff_diagnostics::{AlwaysFixableViolation, Violation}; use ruff_diagnostics::{AlwaysFixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::identifier::Identifier; use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::visitor; use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
@ -649,8 +649,8 @@ where
} }
} }
Expr::Call(ast::ExprCall { func, .. }) => { Expr::Call(ast::ExprCall { func, .. }) => {
if collect_call_path(func).is_some_and(|call_path| { if CallPath::from_expr(func).is_some_and(|call_path| {
matches!(call_path.as_slice(), ["request", "addfinalizer"]) matches!(call_path.segments(), ["request", "addfinalizer"])
}) { }) {
self.addfinalizer_call = Some(expr); self.addfinalizer_call = Some(expr);
}; };

View file

@ -1,6 +1,6 @@
use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::{self as ast, Decorator, Expr, Keyword}; use ruff_python_ast::{self as ast, Decorator, Expr, Keyword};
use ruff_python_ast::call_path::collect_call_path;
use ruff_python_ast::helpers::map_callable; use ruff_python_ast::helpers::map_callable;
use ruff_python_semantic::SemanticModel; use ruff_python_semantic::SemanticModel;
use ruff_python_trivia::PythonWhitespace; use ruff_python_trivia::PythonWhitespace;
@ -9,10 +9,10 @@ pub(super) fn get_mark_decorators(
decorators: &[Decorator], decorators: &[Decorator],
) -> impl Iterator<Item = (&Decorator, &str)> { ) -> impl Iterator<Item = (&Decorator, &str)> {
decorators.iter().filter_map(|decorator| { decorators.iter().filter_map(|decorator| {
let Some(call_path) = collect_call_path(map_callable(&decorator.expression)) else { let Some(call_path) = CallPath::from_expr(map_callable(&decorator.expression)) else {
return None; return None;
}; };
let ["pytest", "mark", marker] = call_path.as_slice() else { let ["pytest", "mark", marker] = call_path.segments() else {
return None; return None;
}; };
Some((decorator, *marker)) Some((decorator, *marker))
@ -22,25 +22,25 @@ pub(super) fn get_mark_decorators(
pub(super) fn is_pytest_fail(call: &Expr, semantic: &SemanticModel) -> bool { pub(super) fn is_pytest_fail(call: &Expr, semantic: &SemanticModel) -> bool {
semantic semantic
.resolve_call_path(call) .resolve_call_path(call)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pytest", "fail"])) .is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "fail"]))
} }
pub(super) fn is_pytest_fixture(decorator: &Decorator, semantic: &SemanticModel) -> bool { pub(super) fn is_pytest_fixture(decorator: &Decorator, semantic: &SemanticModel) -> bool {
semantic semantic
.resolve_call_path(map_callable(&decorator.expression)) .resolve_call_path(map_callable(&decorator.expression))
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pytest", "fixture"])) .is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "fixture"]))
} }
pub(super) fn is_pytest_yield_fixture(decorator: &Decorator, semantic: &SemanticModel) -> bool { pub(super) fn is_pytest_yield_fixture(decorator: &Decorator, semantic: &SemanticModel) -> bool {
semantic semantic
.resolve_call_path(map_callable(&decorator.expression)) .resolve_call_path(map_callable(&decorator.expression))
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pytest", "yield_fixture"])) .is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "yield_fixture"]))
} }
pub(super) fn is_pytest_parametrize(decorator: &Decorator, semantic: &SemanticModel) -> bool { pub(super) fn is_pytest_parametrize(decorator: &Decorator, semantic: &SemanticModel) -> bool {
semantic semantic
.resolve_call_path(map_callable(&decorator.expression)) .resolve_call_path(map_callable(&decorator.expression))
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pytest", "mark", "parametrize"])) .is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "mark", "parametrize"]))
} }
pub(super) fn keyword_is_literal(keyword: &Keyword, literal: &str) -> bool { pub(super) fn keyword_is_literal(keyword: &Keyword, literal: &str) -> bool {

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::visitor; use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{self as ast, Expr, Parameters}; use ruff_python_ast::{self as ast, Expr, Parameters};
@ -107,10 +107,10 @@ fn check_patch_call(call: &ast::ExprCall, index: usize) -> Option<Diagnostic> {
/// PT008 /// PT008
pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option<Diagnostic> { pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option<Diagnostic> {
let call_path = collect_call_path(&call.func)?; let call_path = CallPath::from_expr(&call.func)?;
if matches!( if matches!(
call_path.as_slice(), call_path.segments(),
[ [
"mocker" "mocker"
| "class_mocker" | "class_mocker"
@ -123,7 +123,7 @@ pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option<Diagnostic> {
) { ) {
check_patch_call(call, 1) check_patch_call(call, 1)
} else if matches!( } else if matches!(
call_path.as_slice(), call_path.segments(),
[ [
"mocker" "mocker"
| "class_mocker" | "class_mocker"

View file

@ -1,6 +1,5 @@
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::format_call_path;
use ruff_python_ast::helpers::is_compound_statement; use ruff_python_ast::helpers::is_compound_statement;
use ruff_python_ast::{self as ast, Expr, Stmt, WithItem}; use ruff_python_ast::{self as ast, Expr, Stmt, WithItem};
use ruff_python_semantic::SemanticModel; use ruff_python_semantic::SemanticModel;
@ -155,7 +154,7 @@ impl Violation for PytestRaisesWithoutException {
fn is_pytest_raises(func: &Expr, semantic: &SemanticModel) -> bool { fn is_pytest_raises(func: &Expr, semantic: &SemanticModel) -> bool {
semantic semantic
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pytest", "raises"])) .is_some_and(|call_path| matches!(call_path.segments(), ["pytest", "raises"]))
} }
const fn is_non_trivial_with_body(body: &[Stmt]) -> bool { const fn is_non_trivial_with_body(body: &[Stmt]) -> bool {
@ -231,7 +230,7 @@ fn exception_needs_match(checker: &mut Checker, exception: &Expr) {
.semantic() .semantic()
.resolve_call_path(exception) .resolve_call_path(exception)
.and_then(|call_path| { .and_then(|call_path| {
let call_path = format_call_path(&call_path); let call_path = call_path.to_string();
checker checker
.settings .settings
.flake8_pytest_style .flake8_pytest_style

View file

@ -100,7 +100,7 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
if checker if checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["ctypes", "WinError"])) .is_some_and(|call_path| matches!(call_path.segments(), ["ctypes", "WinError"]))
{ {
return; return;
} }

View file

@ -402,7 +402,7 @@ fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool {
// libraries. // libraries.
if semantic.resolve_call_path(func).is_some_and(|call_path| { if semantic.resolve_call_path(func).is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["" | "builtins" | "sys" | "_thread" | "pytest", "exit"] ["" | "builtins" | "sys" | "_thread" | "pytest", "exit"]
| ["" | "builtins", "quit"] | ["" | "builtins", "quit"]
| ["os" | "posix", "_exit" | "abort"] | ["os" | "posix", "_exit" | "abort"]

View file

@ -199,7 +199,7 @@ fn has_conditional_body(with: &ast::StmtWith, semantic: &SemanticModel) -> bool
return false; return false;
}; };
if let Some(call_path) = semantic.resolve_call_path(func) { if let Some(call_path) = semantic.resolve_call_path(func) {
if call_path.as_slice() == ["contextlib", "suppress"] { if call_path.segments() == ["contextlib", "suppress"] {
return true; return true;
} }
} }

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::{self as ast, Expr}; use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::{BindingKind, ScopeKind}; use ruff_python_semantic::{BindingKind, ScopeKind};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
@ -142,23 +142,23 @@ pub(crate) fn private_member_access(checker: &mut Checker, expr: &Expr) {
// Allow some documented private methods, like `os._exit()`. // Allow some documented private methods, like `os._exit()`.
if let Some(call_path) = checker.semantic().resolve_call_path(expr) { if let Some(call_path) = checker.semantic().resolve_call_path(expr) {
if matches!(call_path.as_slice(), ["os", "_exit"]) { if matches!(call_path.segments(), ["os", "_exit"]) {
return; return;
} }
} }
if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() { if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
// Ignore `super()` calls. // Ignore `super()` calls.
if let Some(call_path) = collect_call_path(func) { if let Some(call_path) = CallPath::from_expr(func) {
if matches!(call_path.as_slice(), ["super"]) { if matches!(call_path.segments(), ["super"]) {
return; return;
} }
} }
} }
if let Some(call_path) = collect_call_path(value) { if let Some(call_path) = CallPath::from_expr(value) {
// Ignore `self` and `cls` accesses. // Ignore `self` and `cls` accesses.
if matches!(call_path.as_slice(), ["self" | "cls" | "mcs"]) { if matches!(call_path.segments(), ["self" | "cls" | "mcs"]) {
return; return;
} }
} }

View file

@ -152,7 +152,7 @@ pub(crate) fn use_capital_environment_variables(checker: &mut Checker, expr: &Ex
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["os", "environ", "get"] | ["os", "getenv"] ["os", "environ", "get"] | ["os", "getenv"]
) )
}) })

View file

@ -102,7 +102,7 @@ fn explicit_with_items(checker: &mut Checker, with_items: &[WithItem]) -> bool {
.resolve_call_path(&expr_call.func) .resolve_call_path(&expr_call.func)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!( matches!(
call_path.as_slice(), call_path.segments(),
["asyncio", "timeout" | "timeout_at"] ["asyncio", "timeout" | "timeout_at"]
| ["anyio", "CancelScope" | "fail_after" | "move_on_after"] | ["anyio", "CancelScope" | "fail_after" | "move_on_after"]
| [ | [

View file

@ -64,7 +64,7 @@ fn match_async_exit_stack(semantic: &SemanticModel) -> bool {
for item in items { for item in items {
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr { if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
if semantic.resolve_call_path(func).is_some_and(|call_path| { if semantic.resolve_call_path(func).is_some_and(|call_path| {
matches!(call_path.as_slice(), ["contextlib", "AsyncExitStack"]) matches!(call_path.segments(), ["contextlib", "AsyncExitStack"])
}) { }) {
return true; return true;
} }
@ -95,7 +95,7 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool {
for item in items { for item in items {
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr { if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
if semantic.resolve_call_path(func).is_some_and(|call_path| { if semantic.resolve_call_path(func).is_some_and(|call_path| {
matches!(call_path.as_slice(), ["contextlib", "ExitStack"]) matches!(call_path.segments(), ["contextlib", "ExitStack"])
}) { }) {
return true; return true;
} }
@ -115,7 +115,7 @@ fn is_open(checker: &mut Checker, func: &Expr) -> bool {
Expr::Call(ast::ExprCall { func, .. }) => checker Expr::Call(ast::ExprCall { func, .. }) => checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pathlib", "Path"])), .is_some_and(|call_path| matches!(call_path.segments(), ["pathlib", "Path"])),
_ => false, _ => false,
} }
} }

View file

@ -74,7 +74,7 @@ pub(crate) fn no_slots_in_namedtuple_subclass(
checker checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["collections", "namedtuple"])) .is_some_and(|call_path| matches!(call_path.segments(), ["collections", "namedtuple"]))
}) { }) {
if !has_slots(&class.body) { if !has_slots(&class.body) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -70,7 +70,7 @@ fn is_str_subclass(bases: &[Expr], semantic: &SemanticModel) -> bool {
let mut is_str_subclass = false; let mut is_str_subclass = false;
for base in bases { for base in bases {
if let Some(call_path) = semantic.resolve_call_path(base) { if let Some(call_path) = semantic.resolve_call_path(base) {
match call_path.as_slice() { match call_path.segments() {
["" | "builtins", "str"] => { ["" | "builtins", "str"] => {
is_str_subclass = true; is_str_subclass = true;
} }

View file

@ -60,7 +60,7 @@ pub(crate) fn no_slots_in_tuple_subclass(checker: &mut Checker, stmt: &Stmt, cla
.semantic() .semantic()
.resolve_call_path(map_subscript(base)) .resolve_call_path(map_subscript(base))
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["" | "builtins", "tuple"]) matches!(call_path.segments(), ["" | "builtins", "tuple"])
|| checker || checker
.semantic() .semantic()
.match_typing_call_path(&call_path, "Tuple") .match_typing_call_path(&call_path, "Tuple")

View file

@ -2,7 +2,7 @@ use ruff_python_ast::Expr;
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::from_qualified_name; use ruff_python_ast::call_path::CallPath;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -67,9 +67,9 @@ pub(crate) fn banned_attribute_access(checker: &mut Checker, expr: &Expr) {
.semantic() .semantic()
.resolve_call_path(expr) .resolve_call_path(expr)
.and_then(|call_path| { .and_then(|call_path| {
banned_api banned_api.iter().find(|(banned_path, ..)| {
.iter() call_path == CallPath::from_qualified_name(banned_path)
.find(|(banned_path, ..)| call_path == from_qualified_name(banned_path)) })
}) })
{ {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -71,7 +71,7 @@ impl MethodName {
impl MethodName { impl MethodName {
pub(super) fn try_from(call_path: &CallPath<'_>) -> Option<Self> { pub(super) fn try_from(call_path: &CallPath<'_>) -> Option<Self> {
match call_path.as_slice() { match call_path.segments() {
["trio", "CancelScope"] => Some(Self::CancelScope), ["trio", "CancelScope"] => Some(Self::CancelScope),
["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully), ["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully),
["trio", "fail_after"] => Some(Self::FailAfter), ["trio", "fail_after"] => Some(Self::FailAfter),

View file

@ -64,7 +64,7 @@ pub(crate) fn unneeded_sleep(checker: &mut Checker, while_stmt: &ast::StmtWhile)
if checker if checker
.semantic() .semantic()
.resolve_call_path(func.as_ref()) .resolve_call_path(func.as_ref())
.is_some_and(|path| matches!(path.as_slice(), ["trio", "sleep" | "sleep_until"])) .is_some_and(|path| matches!(path.segments(), ["trio", "sleep" | "sleep_until"]))
{ {
checker checker
.diagnostics .diagnostics

View file

@ -63,7 +63,7 @@ pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) {
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(call.func.as_ref()) .resolve_call_path(call.func.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["trio", "sleep"])) .is_some_and(|call_path| matches!(call_path.segments(), ["trio", "sleep"]))
{ {
return; return;
} }

View file

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use ruff_diagnostics::Edit; use ruff_diagnostics::Edit;
use ruff_python_ast::call_path::from_qualified_name; use ruff_python_ast::call_path::CallPath;
use ruff_python_ast::helpers::{map_callable, map_subscript}; use ruff_python_ast::helpers::{map_callable, map_subscript};
use ruff_python_ast::{self as ast, Decorator, Expr}; use ruff_python_ast::{self as ast, Decorator, Expr};
use ruff_python_codegen::{Generator, Stylist}; use ruff_python_codegen::{Generator, Stylist};
@ -80,7 +80,7 @@ fn runtime_required_base_class(
analyze::class::any_call_path(class_def, semantic, &|call_path| { analyze::class::any_call_path(class_def, semantic, &|call_path| {
base_classes base_classes
.iter() .iter()
.any(|base_class| from_qualified_name(base_class) == call_path) .any(|base_class| CallPath::from_qualified_name(base_class) == call_path)
}) })
} }
@ -99,7 +99,7 @@ fn runtime_required_decorators(
.is_some_and(|call_path| { .is_some_and(|call_path| {
decorators decorators
.iter() .iter()
.any(|base_class| from_qualified_name(base_class) == call_path) .any(|base_class| CallPath::from_qualified_name(base_class) == call_path)
}) })
}) })
} }
@ -121,14 +121,14 @@ pub(crate) fn is_dataclass_meta_annotation(annotation: &Expr, semantic: &Semanti
semantic semantic
.resolve_call_path(map_callable(&decorator.expression)) .resolve_call_path(map_callable(&decorator.expression))
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["dataclasses", "dataclass"]) matches!(call_path.segments(), ["dataclasses", "dataclass"])
}) })
}) { }) {
// Determine whether the annotation is `typing.ClassVar` or `dataclasses.InitVar`. // Determine whether the annotation is `typing.ClassVar` or `dataclasses.InitVar`.
return semantic return semantic
.resolve_call_path(map_subscript(annotation)) .resolve_call_path(map_subscript(annotation))
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["dataclasses", "InitVar"]) matches!(call_path.segments(), ["dataclasses", "InitVar"])
|| semantic.match_typing_call_path(&call_path, "ClassVar") || semantic.match_typing_call_path(&call_path, "ClassVar")
}); });
} }
@ -156,7 +156,7 @@ pub(crate) fn is_singledispatch_interface(
semantic semantic
.resolve_call_path(&decorator.expression) .resolve_call_path(&decorator.expression)
.is_some_and(|call_path| { .is_some_and(|call_path| {
matches!(call_path.as_slice(), ["functools", "singledispatch"]) matches!(call_path.segments(), ["functools", "singledispatch"])
}) })
}) })
} }

View file

@ -79,7 +79,7 @@ pub(crate) fn os_sep_split(checker: &mut Checker, call: &ast::ExprCall) {
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(sep) .resolve_call_path(sep)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "sep"])) .is_some_and(|call_path| matches!(call_path.segments(), ["os", "sep"]))
{ {
return; return;
} }

View file

@ -47,7 +47,7 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E
if !checker if !checker
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pathlib", "Path" | "PurePath"])) .is_some_and(|call_path| matches!(call_path.segments(), ["pathlib", "Path" | "PurePath"]))
{ {
return; return;
} }

View file

@ -20,7 +20,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &mut Checker, call: &ExprCall) {
checker checker
.semantic() .semantic()
.resolve_call_path(&call.func) .resolve_call_path(&call.func)
.and_then(|call_path| match call_path.as_slice() { .and_then(|call_path| match call_path.segments() {
// PTH100 // PTH100
["os", "path", "abspath"] => Some(OsPathAbspath.into()), ["os", "path", "abspath"] => Some(OsPathAbspath.into()),
// PTH101 // PTH101

Some files were not shown because too many files have changed in this diff Show more