diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 970f33e2aa..4bd276eb7f 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -755,7 +755,7 @@ 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.as_slice() { + match call_path.segments() { ["", "NameError"] => { handled_exceptions |= Exceptions::NAME_ERROR; } @@ -1086,7 +1086,7 @@ where { Some(typing::Callable::TypedDict) } else if matches!( - call_path.as_slice(), + call_path.segments(), [ "mypy_extensions", "Arg" @@ -1098,7 +1098,7 @@ where ] ) { Some(typing::Callable::MypyExtension) - } else if matches!(call_path.as_slice(), ["", "bool"]) { + } else if matches!(call_path.segments(), ["", "bool"]) { Some(typing::Callable::Bool) } else { None diff --git a/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs b/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs index 76ebb405a2..7f90283f99 100644 --- a/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs +++ b/crates/ruff_linter/src/rules/airflow/rules/task_variable_name.rs @@ -69,7 +69,7 @@ pub(crate) fn variable_name_task_id( if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_2020/helpers.rs b/crates/ruff_linter/src/rules/flake8_2020/helpers.rs index 4a778c5640..4e0fdad7d6 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/helpers.rs @@ -5,5 +5,5 @@ use ruff_python_semantic::SemanticModel; pub(super) fn is_sys(expr: &Expr, target: &str, semantic: &SemanticModel) -> bool { semantic .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]) } diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs index ad9736437b..433f6a2b62 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs @@ -54,7 +54,7 @@ pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) { if checker .semantic() .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 .diagnostics diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs index 3fe925f2f1..01b7be8085 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_http_call.rs @@ -43,7 +43,7 @@ impl Violation for BlockingHttpCallInAsyncFunction { fn is_blocking_http_call(call_path: &CallPath) -> bool { matches!( - call_path.as_slice(), + call_path.segments(), ["urllib", "request", "urlopen"] | [ "httpx" | "requests", diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs index b4db91dccc..db0d82ea2d 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs @@ -62,7 +62,7 @@ pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) { fn is_unsafe_os_method(call_path: &CallPath) -> bool { matches!( - call_path.as_slice(), + call_path.segments(), [ "os", "popen" diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs index 2aa2a3c81b..ae3f98f83b 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/open_sleep_or_subprocess_call.rs @@ -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 { semantic.resolve_call_path(func).is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["", "open"] | ["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 { return false; }; - if call_path.as_slice() == ["pathlib", "Path"] { + if call_path.segments() == ["pathlib", "Path"] { return true; } } @@ -124,5 +124,5 @@ fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool { semantic .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"]) } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/helpers.rs b/crates/ruff_linter/src/rules/flake8_bandit/helpers.rs index 53ba6eddbf..1f16f124c0 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/helpers.rs @@ -24,12 +24,12 @@ pub(super) fn is_untyped_exception(type_: Option<&Expr>, semantic: &SemanticMode 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.as_slice(), ["", "Exception" | "BaseException"]) + matches!(call_path.segments(), ["", "Exception" | "BaseException"]) }) }) } else { semantic.resolve_call_path(type_).is_some_and(|call_path| { - matches!(call_path.as_slice(), ["", "Exception" | "BaseException"]) + matches!(call_path.segments(), ["", "Exception" | "BaseException"]) }) } }) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs index 1496b42261..c65e3b75b3 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs @@ -67,7 +67,7 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall) if checker .semantic() .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) { match parse_mask(mode_arg, checker.semantic()) { @@ -102,7 +102,7 @@ const WRITE_WORLD: u16 = 0o2; const EXECUTE_GROUP: u16 = 0o10; fn py_stat(call_path: &CallPath) -> Option { - match call_path.as_slice() { + match call_path.segments() { ["stat", "ST_MODE"] => Some(0o0), ["stat", "S_IFDOOR"] => Some(0o0), ["stat", "S_IFPORT"] => Some(0o0), diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs index fa142c07a5..4ab3a2d622 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs @@ -45,7 +45,7 @@ pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) { .resolve_call_path(&call.func) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["django", "db", "models", "expressions", "RawSQL"] ) }) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs index 6271e47de1..d1680b356c 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/exec_used.rs @@ -36,7 +36,7 @@ pub(crate) fn exec_used(checker: &mut Checker, func: &Expr) { if checker .semantic() .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 .diagnostics diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs index a1517f26c0..3c3908f0bc 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/flask_debug_true.rs @@ -65,7 +65,7 @@ pub(crate) fn flask_debug_true(checker: &mut Checker, call: &ExprCall) { } 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 .diagnostics diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs index e0a66fec19..291387bc70 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_tmp_directory.rs @@ -75,7 +75,7 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike) if checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs index f362858fc6..8e0b1e6492 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hashlib_insecure_hash_functions.rs @@ -65,7 +65,7 @@ pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast: checker .semantic() .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", "md4"] => Some(HashlibCall::WeakHash("md4")), ["hashlib", "md5"] => Some(HashlibCall::WeakHash("md5")), diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs index 2cd35d1049..1e6beac11a 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs @@ -62,7 +62,7 @@ pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCal if checker .semantic() .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") { match &keyword.value { diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs index 3577a87a95..77334b2abe 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs @@ -43,7 +43,7 @@ pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast:: if checker .semantic() .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() { return; diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs index b101792b22..186f8d96c6 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/mako_templates.rs @@ -48,7 +48,7 @@ pub(crate) fn mako_templates(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() .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 .diagnostics diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs index d0ccc8c913..0b6a7e530e 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/paramiko_calls.rs @@ -40,7 +40,7 @@ pub(crate) fn paramiko_call(checker: &mut Checker, func: &Expr) { if checker .semantic() .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 .diagnostics diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs index 56f02b6655..295bec1d80 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs @@ -50,7 +50,7 @@ pub(crate) fn request_with_no_cert_validation(checker: &mut Checker, call: &ast: if let Some(target) = checker .semantic() .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"] => { Some("requests") } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs index 5e37a2b693..0af9c93162 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs @@ -55,7 +55,7 @@ pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCal .resolve_call_path(&call.func) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete" diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs index 4b28ffb13b..89d4a316b2 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/shell_injection.rs @@ -420,7 +420,7 @@ enum CallKind { fn get_call_kind(func: &Expr, semantic: &SemanticModel) -> Option { semantic .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 { "os" => match submodule { "execl" | "execle" | "execlp" | "execlpe" | "execv" | "execve" | "execvp" diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs index 23d92d96fa..73325af060 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs @@ -46,7 +46,7 @@ pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall) .semantic() .resolve_call_path(&call.func) .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") { diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs index 0f43bffb9f..cebbf94ce0 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs @@ -47,7 +47,7 @@ pub(crate) fn snmp_weak_cryptography(checker: &mut Checker, call: &ast::ExprCall .semantic() .resolve_call_path(&call.func) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["pysnmp", "hlapi", "UsmUserData"]) + matches!(call_path.segments(), ["pysnmp", "hlapi", "UsmUserData"]) }) { checker diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs index 03b9eb3473..9e1f863fa1 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssh_no_host_key_verification.rs @@ -63,7 +63,7 @@ pub(crate) fn ssh_no_host_key_verification(checker: &mut Checker, call: &ExprCal .resolve_call_path(map_callable(policy_argument)) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["paramiko", "client", "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| { matches!( - call_path.as_slice(), + call_path.segments(), ["paramiko", "client", "SSHClient"] | ["paramiko", "SSHClient"] ) }) { diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs index d0b94e07da..6fb26a83e5 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_insecure_version.rs @@ -52,7 +52,7 @@ pub(crate) fn ssl_insecure_version(checker: &mut Checker, call: &ExprCall) { let Some(keyword) = checker .semantic() .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"), ["OpenSSL", "SSL", "Context"] => Some("method"), _ => None, diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs index ca8623dca8..221d102e13 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/ssl_with_no_version.rs @@ -40,7 +40,7 @@ pub(crate) fn ssl_with_no_version(checker: &mut Checker, call: &ExprCall) { if checker .semantic() .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() { checker diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs index 312a55bedc..143c982dda 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs @@ -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 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| { - match call_path.as_slice() { + match call_path.segments() { // Pickle ["pickle" | "dill", "load" | "loads" | "Unpickler"] | ["shelve", "open" | "DbfilenameShelf"] | @@ -908,7 +908,7 @@ pub(crate) fn suspicious_function_decorator(checker: &mut Checker, decorator: &D .semantic() .resolve_call_path(&decorator.expression) .and_then(|call_path| { - match call_path.as_slice() { + match call_path.segments() { // MarkSafe ["django", "utils", "safestring" | "html", "mark_safe"] => { Some(SuspiciousMarkSafeUsage.into()) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs index 22b100c1d8..3b2b72b53b 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/unsafe_yaml_load.rs @@ -63,7 +63,7 @@ pub(crate) fn unsafe_yaml_load(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() .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 !checker @@ -71,7 +71,7 @@ pub(crate) fn unsafe_yaml_load(checker: &mut Checker, call: &ast::ExprCall) { .resolve_call_path(loader_arg) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["yaml", "SafeLoader" | "CSafeLoader"] | ["yaml", "loader", "SafeLoader" | "CSafeLoader"] ) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs index b971619173..d24e7c0c43 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/weak_cryptographic_key.rs @@ -102,7 +102,7 @@ fn extract_cryptographic_key( call: &ExprCall, ) -> Option<(CryptographicKey, TextRange)> { 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"] => { match *function { "dsa" => { @@ -118,7 +118,7 @@ fn extract_cryptographic_key( let ExprAttribute { attr, value, .. } = argument.as_attribute_expr()?; let call_path = checker.semantic().resolve_call_path(value)?; if matches!( - call_path.as_slice(), + call_path.segments(), ["cryptography", "hazmat", "primitives", "asymmetric", "ec"] ) { Some(( diff --git a/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs b/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs index 3eab03f29a..e15e93ddda 100644 --- a/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs +++ b/crates/ruff_linter/src/rules/flake8_blind_except/rules/blind_except.rs @@ -141,7 +141,7 @@ pub(crate) fn blind_except( if checker .semantic() .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", "error"] => { if let Some(keyword) = arguments.find_keyword("exc_info") { diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs index 61134cd316..b144851070 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_default_value_positional_argument.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{Diagnostic, 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_semantic::analyze::visibility; use ruff_text_size::Ranged; @@ -119,8 +119,8 @@ pub(crate) fn boolean_default_value_positional_argument( { // Allow Boolean defaults in setters. if decorator_list.iter().any(|decorator| { - collect_call_path(&decorator.expression) - .is_some_and(|call_path| call_path.as_slice() == [name, "setter"]) + CallPath::from_expr(&decorator.expression) + .is_some_and(|call_path| call_path.segments() == [name, "setter"]) }) { return; } diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs index f4bf4a2963..94c7ab0e0c 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/rules/boolean_type_hint_positional_argument.rs @@ -3,7 +3,7 @@ use ruff_python_ast::{self as ast, Decorator, Expr, ParameterWithDefault, Parame use ruff_diagnostics::Diagnostic; use ruff_diagnostics::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::SemanticModel; use ruff_text_size::Ranged; @@ -136,8 +136,8 @@ pub(crate) fn boolean_type_hint_positional_argument( // Allow Boolean type hints in setters. if decorator_list.iter().any(|decorator| { - collect_call_path(&decorator.expression) - .is_some_and(|call_path| call_path.as_slice() == [name, "setter"]) + CallPath::from_expr(&decorator.expression) + .is_some_and(|call_path| call_path.segments() == [name, "setter"]) }) { return; } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs index ac98296434..dd265e1127 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/abstract_base_class.rs @@ -110,11 +110,11 @@ fn is_abc_class(bases: &[Expr], keywords: &[Keyword], semantic: &SemanticModel) keyword.arg.as_ref().is_some_and(|arg| arg == "metaclass") && semantic .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| { semantic .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"])) }) } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs index 7dc21a544f..9932984093 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_raises_exception.rs @@ -98,7 +98,7 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem]) let Some(exception) = checker .semantic() .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), ["", "BaseException"] => Some(ExceptionKind::BaseException), _ => None, @@ -113,7 +113,7 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem]) } else if checker .semantic() .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() { AssertionKind::PytestRaises diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs index 083954116a..5f8448c49f 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/cached_instance_method.rs @@ -72,7 +72,7 @@ impl Violation for CachedInstanceMethod { fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool { 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"]) }) } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs index 3f708002e2..947ff280c5 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs @@ -6,7 +6,6 @@ use rustc_hash::{FxHashMap, FxHashSet}; use ruff_diagnostics::{AlwaysFixableViolation, Violation}; use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::call_path; use ruff_python_ast::call_path::CallPath; use crate::checkers::ast::Checker; @@ -124,7 +123,7 @@ fn duplicate_handler_exceptions<'a>( let mut duplicates: FxHashSet = FxHashSet::default(); let mut unique_elts: Vec<&Expr> = Vec::default(); 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) { duplicates.insert(call_path); } else { @@ -141,7 +140,7 @@ fn duplicate_handler_exceptions<'a>( DuplicateHandlerException { names: duplicates .into_iter() - .map(|call_path| call_path.join(".")) + .map(|call_path| call_path.segments().join(".")) .sorted() .collect::>(), }, @@ -184,7 +183,7 @@ pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHand }; match type_.as_ref() { 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) { duplicates.entry(call_path).or_default().push(type_); } else { @@ -210,7 +209,7 @@ pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHand for expr in exprs { checker.diagnostics.push(Diagnostic::new( DuplicateTryBlockException { - name: name.join("."), + name: name.segments().join("."), }, expr.range(), )); diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs index 760d9f6b6c..53016ceb79 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/function_call_in_argument_default.rs @@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange}; use ruff_diagnostics::Violation; use ruff_diagnostics::{Diagnostic, DiagnosticKind}; 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::Visitor; use ruff_python_semantic::analyze::typing::{ @@ -128,7 +128,7 @@ pub(crate) fn function_call_in_argument_default(checker: &mut Checker, parameter .flake8_bugbear .extend_immutable_calls .iter() - .map(|target| from_qualified_name(target)) + .map(|target| CallPath::from_qualified_name(target)) .collect(); let mut visitor = ArgumentDefaultVisitor::new(checker.semantic(), &extend_immutable_calls); diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs index db495500f4..1b84980cf8 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs @@ -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_macros::{derive_message_formats, violation}; 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 .extend_immutable_calls .iter() - .map(|target| from_qualified_name(target)) + .map(|target| CallPath::from_qualified_name(target)) .collect(); if is_mutable_expr(default, checker.semantic()) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs index aa2a9b9e5c..e23b17b308 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs @@ -41,7 +41,7 @@ pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, call: &ast::ExprCall if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs index ea25496e91..7e13a4c9f1 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs @@ -64,7 +64,7 @@ pub(crate) fn re_sub_positional_args(checker: &mut Checker, call: &ast::ExprCall let Some(method) = checker .semantic() .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", "subn"] => Some(Method::Subn), ["re", "split"] => Some(Method::Split), diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs index 3bc93c22eb..88e0d8f563 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/reuse_of_groupby_generator.rs @@ -314,7 +314,7 @@ pub(crate) fn reuse_of_groupby_generator( if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs index a5761990ac..87064fb167 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/useless_contextlib_suppress.rs @@ -60,7 +60,7 @@ pub(crate) fn useless_contextlib_suppress( && checker .semantic() .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 .diagnostics diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs index 6a58aa7e89..c8cb82b3f7 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs @@ -100,7 +100,7 @@ fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool { }; semantic.resolve_call_path(func).is_some_and(|call_path| { - match call_path.as_slice() { + match call_path.segments() { ["itertools", "cycle" | "count"] => true, ["itertools", "repeat"] => { // Ex) `itertools.repeat(1)` diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs index 82a64d791b..022ee502c1 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs @@ -66,7 +66,7 @@ pub(crate) fn call_date_fromtimestamp(checker: &mut Checker, func: &Expr, locati .semantic() .resolve_call_path(func) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["datetime", "date", "fromtimestamp"]) + matches!(call_path.segments(), ["datetime", "date", "fromtimestamp"]) }) { checker diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs index d2d58df17d..47b4c724b1 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs @@ -64,7 +64,7 @@ pub(crate) fn call_date_today(checker: &mut Checker, func: &Expr, location: Text if checker .semantic() .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 .diagnostics diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs index a2ffedf827..fda8c13d67 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs @@ -70,7 +70,7 @@ pub(crate) fn call_datetime_fromtimestamp(checker: &mut Checker, call: &ast::Exp .resolve_call_path(&call.func) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["datetime", "datetime", "fromtimestamp"] ) }) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs index 4a09232a09..793cbb2406 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs @@ -64,7 +64,7 @@ pub(crate) fn call_datetime_now_without_tzinfo(checker: &mut Checker, call: &ast if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs index eaac878019..7fbb606b1b 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs @@ -73,7 +73,7 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: & .semantic() .resolve_call_path(&call.func) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["datetime", "datetime", "strptime"]) + matches!(call_path.segments(), ["datetime", "datetime", "strptime"]) }) { return; diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs index 6909a8d3a4..567c6cdcfd 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs @@ -63,7 +63,7 @@ pub(crate) fn call_datetime_today(checker: &mut Checker, func: &Expr, location: if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs index b61ab346bd..7c7c0865ff 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs @@ -73,7 +73,7 @@ pub(crate) fn call_datetime_utcfromtimestamp( .resolve_call_path(func) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["datetime", "datetime", "utcfromtimestamp"] ) }) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs index 9d74fca60a..e8fc86eae9 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs @@ -67,7 +67,7 @@ pub(crate) fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location: if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs index 5c7023b5a8..253bd3d60f 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs @@ -60,7 +60,7 @@ pub(crate) fn call_datetime_without_tzinfo(checker: &mut Checker, call: &ast::Ex if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs b/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs index 3656bbb52b..52089715a0 100644 --- a/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs +++ b/crates/ruff_linter/src/rules/flake8_debugger/rules/debugger.rs @@ -2,7 +2,7 @@ use ruff_python_ast::{Expr, Stmt}; use ruff_diagnostics::{Diagnostic, 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 crate::checkers::ast::Checker; @@ -53,7 +53,7 @@ pub(crate) fn debugger_call(checker: &mut Checker, expr: &Expr, func: &Expr) { .resolve_call_path(func) .and_then(|call_path| { if is_debugger_call(&call_path) { - Some(DebuggerUsingType::Call(format_call_path(&call_path))) + Some(DebuggerUsingType::Call(call_path.to_string())) } else { 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. pub(crate) fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option { if let Some(module) = module { - let mut call_path: CallPath = from_unqualified_name(module); - call_path.push(name); + let mut builder = CallPathBuilder::from_path(CallPath::from_unqualified_name(module)); + builder.push(name); + let call_path = builder.build(); if is_debugger_call(&call_path) { return Some(Diagnostic::new( Debugger { - using_type: DebuggerUsingType::Import(format_call_path(&call_path)), + using_type: DebuggerUsingType::Import(call_path.to_string()), }, stmt.range(), )); } } else { - let call_path: CallPath = from_unqualified_name(name); + let call_path: CallPath = CallPath::from_unqualified_name(name); if is_debugger_import(&call_path) { 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 { matches!( - call_path.as_slice(), + call_path.segments(), ["pdb" | "pudb" | "ipdb", "set_trace"] | ["ipdb", "sset_trace"] | ["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 // than (e.g.) `import celery.contrib.rdb`. matches!( - call_path.as_slice(), + call_path.segments(), ["pdb" | "pudb" | "ipdb" | "debugpy" | "ptvsd"] | ["IPython", "terminal", "embed"] | ["IPython", "frontend", "terminal", "embed",] diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/helpers.rs b/crates/ruff_linter/src/rules/flake8_django/rules/helpers.rs index 073371c1bc..e1d68c4273 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/helpers.rs @@ -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. pub(super) fn is_model(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { 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 { analyze::class::any_call_path(class_def, semantic, &|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["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 { semantic.resolve_call_path(expr).is_some_and(|call_path| { call_path - .as_slice() + .segments() .starts_with(&["django", "db", "models"]) }) } @@ -34,7 +34,7 @@ pub(super) fn get_model_field_name<'a>( semantic: &'a SemanticModel, ) -> Option<&'a str> { 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"]) { return None; } diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs b/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs index 53d4563f43..3c80332a82 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs @@ -52,7 +52,7 @@ pub(crate) fn locals_in_render_function(checker: &mut Checker, call: &ast::ExprC if !checker .semantic() .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; } @@ -73,5 +73,5 @@ fn is_locals_call(expr: &Expr, semantic: &SemanticModel) -> bool { }; semantic .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"])) } diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs b/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs index c67e8c18fe..eedd93b838 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs @@ -63,7 +63,7 @@ pub(crate) fn non_leading_receiver_decorator(checker: &mut Checker, decorator_li .semantic() .resolve_call_path(&call.func) .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 { diff --git a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs index ceb47b1471..9a088494e3 100644 --- a/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs +++ b/crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs @@ -2,7 +2,6 @@ use ruff_python_ast::Expr; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::call_path::format_call_path; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -82,7 +81,7 @@ pub(crate) fn future_rewritable_type_annotation(checker: &mut Checker, expr: &Ex let name = checker .semantic() .resolve_call_path(expr) - .map(|binding| format_call_path(&binding)); + .map(|binding| binding.to_string()); if let Some(name) = name { checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs index 71760b3326..46992de2ef 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs @@ -62,7 +62,7 @@ pub(crate) fn direct_logger_instantiation(checker: &mut Checker, call: &ast::Exp if checker .semantic() .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()); diagnostic.try_set_fix(|| { diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs index ed4151befb..cff88e5ab9 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/exception_without_exc_info.rs @@ -63,7 +63,7 @@ pub(crate) fn exception_without_exc_info(checker: &mut Checker, call: &ExprCall) if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs index 372e0be667..dadffe3473 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs @@ -78,7 +78,7 @@ pub(crate) fn invalid_get_logger_argument(checker: &mut Checker, call: &ast::Exp if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs index 394864537d..6a08cf811e 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs @@ -56,7 +56,7 @@ pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) { if checker .semantic() .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()); diagnostic.try_set_fix(|| { diff --git a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs index 12b3ad1ebb..ce3a3d8bee 100644 --- a/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs +++ b/crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs @@ -111,7 +111,7 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) { if checker .semantic() .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() { 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 { return; }; - let ["logging", attribute] = call_path.as_slice() else { + let ["logging", attribute] = call_path.segments() else { return; }; let Some(call_type) = LoggingCallType::from_attribute(attribute) else { diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs index 36f54f444f..f960db6ffc 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs @@ -63,7 +63,7 @@ pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stm checker .semantic() .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; } @@ -78,7 +78,7 @@ pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stm if checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs index d8866f8390..994e1afeb1 100644 --- a/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs +++ b/crates/ruff_linter/src/rules/flake8_print/rules/print_call.rs @@ -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); if call_path .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"`, // 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( true, |call_path| { - call_path.as_slice() != ["sys", "stdout"] - && call_path.as_slice() != ["sys", "stderr"] + call_path.segments() != ["sys", "stdout"] + && call_path.segments() != ["sys", "stderr"] }, ) { return; @@ -121,7 +121,7 @@ pub(crate) fn print_call(checker: &mut Checker, call: &ast::ExprCall) { Diagnostic::new(Print, call.func.range()) } else if call_path .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()) } else { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs index 29a064473f..45cc221c6d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_generator_return_type.rs @@ -128,7 +128,7 @@ pub(crate) fn bad_generator_return_type( let Some(call_path) = semantic.resolve_call_path(map_subscript(returns)) else { return; }; - match (name, call_path.as_slice()) { + match (name, call_path.segments()) { ("__iter__", ["typing", "Generator"]) => { (Method::Iter, Module::Typing, Generator::Generator) } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs index 0262770f81..236635df81 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/bad_version_info_comparison.rs @@ -76,7 +76,7 @@ pub(crate) fn bad_version_info_comparison(checker: &mut Checker, test: &Expr) { if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs index ca701099d3..d1c2ca8f67 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs @@ -58,7 +58,7 @@ pub(crate) fn collections_named_tuple(checker: &mut Checker, expr: &Expr) { if checker .semantic() .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 .diagnostics diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs index 87e68fec1e..4b3fe264ef 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/complex_if_statement_in_stub.rs @@ -69,7 +69,7 @@ pub(crate) fn complex_if_statement_in_stub(checker: &mut Checker, test: &Expr) { .semantic() .resolve_call_path(left) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["sys", "version_info" | "platform"]) + matches!(call_path.segments(), ["sys", "version_info" | "platform"]) }) { return; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs index b4081e3c6a..d6f8695b3b 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/exit_annotations.rs @@ -309,7 +309,7 @@ fn is_object_or_unused(expr: &Expr, semantic: &SemanticModel) -> bool { .as_ref() .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["" | "builtins", "object"] | ["_typeshed", "Unused"] ) }) @@ -320,7 +320,7 @@ fn is_base_exception(expr: &Expr, semantic: &SemanticModel) -> bool { semantic .resolve_call_path(expr) .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. @@ -328,7 +328,7 @@ fn is_traceback_type(expr: &Expr, semantic: &SemanticModel) -> bool { semantic .resolve_call_path(expr) .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]`. @@ -341,7 +341,7 @@ fn is_base_exception_type(expr: &Expr, semantic: &SemanticModel) -> bool { || semantic .resolve_call_path(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"])) { is_base_exception(slice, semantic) } else { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs index db954557ef..06007a9e8b 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/iter_method_return_iterable.rs @@ -92,12 +92,12 @@ pub(crate) fn iter_method_return_iterable(checker: &mut Checker, definition: &De .is_some_and(|call_path| { if is_async { matches!( - call_path.as_slice(), + call_path.segments(), ["typing", "AsyncIterable"] | ["collections", "abc", "AsyncIterable"] ) } else { matches!( - call_path.as_slice(), + call_path.segments(), ["typing", "Iterable"] | ["collections", "abc", "Iterable"] ) } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs index d0766353df..9c08f7b43e 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs @@ -233,7 +233,7 @@ fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool fn is_metaclass_base(base: &Expr, semantic: &SemanticModel) -> bool { semantic.resolve_call_path(base).is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["" | "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)) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["typing", "Iterator"] | ["collections", "abc", "Iterator"] ) }) @@ -295,7 +295,7 @@ fn is_iterable(expr: &Expr, semantic: &SemanticModel) -> bool { .resolve_call_path(map_subscript(expr)) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["typing", "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)) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["typing", "AsyncIterator"] | ["collections", "abc", "AsyncIterator"] ) }) @@ -325,7 +325,7 @@ fn is_async_iterable(expr: &Expr, semantic: &SemanticModel) -> bool { .resolve_call_path(map_subscript(expr)) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["typing", "AsyncIterable" | "AsyncIterator"] | ["collections", "abc", "AsyncIterable" | "AsyncIterator"] ) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index 834e7e5080..bb1744388b 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -94,7 +94,7 @@ fn check_annotation(checker: &mut Checker, annotation: &Expr) { return; }; - match call_path.as_slice() { + match call_path.segments() { ["" | "builtins", "int"] => has_int = true, ["" | "builtins", "float"] => has_float = true, ["" | "builtins", "complex"] => has_complex = true, diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs index 392a07be8f..6f37244a55 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -249,12 +249,12 @@ impl AlwaysFixableViolation for TypeAliasWithoutAnnotation { } 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 { matches!( - call_path.as_slice(), + call_path.segments(), ["math", "inf" | "nan" | "e" | "pi" | "tau"] | [ "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| { matches!( - call_path.as_slice(), + call_path.segments(), [ "typing" | "typing_extensions", "TypeVar" | "TypeVarTuple" | "NewType" | "ParamSpec" @@ -483,7 +483,7 @@ fn is_enum(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { .resolve_call_path(map_subscript(expr)) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "enum", "Enum" | "Flag" | "IntEnum" | "IntFlag" | "StrEnum" | "ReprEnum" diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs index ee8a0de530..197032e8ed 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/str_or_repr_defined_in_stub.rs @@ -82,7 +82,7 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) { .semantic() .resolve_call_path(returns) .map_or(true, |call_path| { - !matches!(call_path.as_slice(), ["" | "builtins", "str"]) + !matches!(call_path.segments(), ["" | "builtins", "str"]) }) { return; diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs index 2548611836..63572c9643 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs @@ -87,7 +87,7 @@ fn is_warnings_dot_deprecated(expr: Option<&ast::Expr>, semantic: &SemanticModel .resolve_call_path(&call.func) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["warnings" | "typing_extensions", "deprecated"] ) }) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs index d4cd2cde2c..d2bcc23408 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs @@ -81,7 +81,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr) if checker .semantic() .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()); } else { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs index 5129d1366f..160dbd5e70 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_platform.rs @@ -108,7 +108,7 @@ pub(crate) fn unrecognized_platform(checker: &mut Checker, test: &Expr) { if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs index ec4c5c5543..b8ff0c87ab 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unrecognized_version_info.rs @@ -136,7 +136,7 @@ pub(crate) fn unrecognized_version_info(checker: &mut Checker, test: &Expr) { if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs index f30773733f..bdc491e687 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs @@ -3,7 +3,7 @@ use std::fmt; use ruff_diagnostics::{AlwaysFixableViolation, Violation}; use ruff_diagnostics::{Diagnostic, Edit, Fix}; 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::visitor; use ruff_python_ast::visitor::Visitor; @@ -649,8 +649,8 @@ where } } Expr::Call(ast::ExprCall { func, .. }) => { - if collect_call_path(func).is_some_and(|call_path| { - matches!(call_path.as_slice(), ["request", "addfinalizer"]) + if CallPath::from_expr(func).is_some_and(|call_path| { + matches!(call_path.segments(), ["request", "addfinalizer"]) }) { self.addfinalizer_call = Some(expr); }; diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/helpers.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/helpers.rs index 9ac1291353..59f85c0a35 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/helpers.rs @@ -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::call_path::collect_call_path; use ruff_python_ast::helpers::map_callable; use ruff_python_semantic::SemanticModel; use ruff_python_trivia::PythonWhitespace; @@ -9,10 +9,10 @@ pub(super) fn get_mark_decorators( decorators: &[Decorator], ) -> impl Iterator { 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; }; - let ["pytest", "mark", marker] = call_path.as_slice() else { + let ["pytest", "mark", marker] = call_path.segments() else { return None; }; Some((decorator, *marker)) @@ -22,25 +22,25 @@ pub(super) fn get_mark_decorators( pub(super) fn is_pytest_fail(call: &Expr, semantic: &SemanticModel) -> bool { semantic .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 { semantic .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 { semantic .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 { semantic .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 { diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs index 8232ed794f..ee9e418cff 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/patch.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{Diagnostic, 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::Visitor; use ruff_python_ast::{self as ast, Expr, Parameters}; @@ -107,10 +107,10 @@ fn check_patch_call(call: &ast::ExprCall, index: usize) -> Option { /// PT008 pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option { - let call_path = collect_call_path(&call.func)?; + let call_path = CallPath::from_expr(&call.func)?; if matches!( - call_path.as_slice(), + call_path.segments(), [ "mocker" | "class_mocker" @@ -123,7 +123,7 @@ pub(crate) fn patch_with_lambda(call: &ast::ExprCall) -> Option { ) { check_patch_call(call, 1) } else if matches!( - call_path.as_slice(), + call_path.segments(), [ "mocker" | "class_mocker" diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs index 254db5b06c..a1344263e0 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs @@ -1,6 +1,5 @@ use ruff_diagnostics::{Diagnostic, 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::{self as ast, Expr, Stmt, WithItem}; use ruff_python_semantic::SemanticModel; @@ -155,7 +154,7 @@ impl Violation for PytestRaisesWithoutException { fn is_pytest_raises(func: &Expr, semantic: &SemanticModel) -> bool { semantic .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 { @@ -231,7 +230,7 @@ fn exception_needs_match(checker: &mut Checker, exception: &Expr) { .semantic() .resolve_call_path(exception) .and_then(|call_path| { - let call_path = format_call_path(&call_path); + let call_path = call_path.to_string(); checker .settings .flake8_pytest_style diff --git a/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs b/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs index 9c13127127..8ed01447b4 100644 --- a/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_raise/rules/unnecessary_paren_on_raise_exception.rs @@ -100,7 +100,7 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr: if checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index c7d8764c06..bf211d3d18 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -402,7 +402,7 @@ fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool { // libraries. if semantic.resolve_call_path(func).is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["" | "builtins" | "sys" | "_thread" | "pytest", "exit"] | ["" | "builtins", "quit"] | ["os" | "posix", "_exit" | "abort"] diff --git a/crates/ruff_linter/src/rules/flake8_return/visitor.rs b/crates/ruff_linter/src/rules/flake8_return/visitor.rs index 653364ff1a..25a5d07717 100644 --- a/crates/ruff_linter/src/rules/flake8_return/visitor.rs +++ b/crates/ruff_linter/src/rules/flake8_return/visitor.rs @@ -199,7 +199,7 @@ fn has_conditional_body(with: &ast::StmtWith, semantic: &SemanticModel) -> bool return false; }; 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; } } diff --git a/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs b/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs index ccabe9ba32..37f6898a6c 100644 --- a/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs +++ b/crates/ruff_linter/src/rules/flake8_self/rules/private_member_access.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{Diagnostic, 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_semantic::{BindingKind, ScopeKind}; 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()`. 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; } } if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() { // Ignore `super()` calls. - if let Some(call_path) = collect_call_path(func) { - if matches!(call_path.as_slice(), ["super"]) { + if let Some(call_path) = CallPath::from_expr(func) { + if matches!(call_path.segments(), ["super"]) { return; } } } - if let Some(call_path) = collect_call_path(value) { + if let Some(call_path) = CallPath::from_expr(value) { // Ignore `self` and `cls` accesses. - if matches!(call_path.as_slice(), ["self" | "cls" | "mcs"]) { + if matches!(call_path.segments(), ["self" | "cls" | "mcs"]) { return; } } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs index 669be14149..07190acb67 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs @@ -152,7 +152,7 @@ pub(crate) fn use_capital_environment_variables(checker: &mut Checker, expr: &Ex .resolve_call_path(func) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["os", "environ", "get"] | ["os", "getenv"] ) }) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs index fd1afd9d46..fd720f03ab 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_with.rs @@ -102,7 +102,7 @@ fn explicit_with_items(checker: &mut Checker, with_items: &[WithItem]) -> bool { .resolve_call_path(&expr_call.func) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["asyncio", "timeout" | "timeout_at"] | ["anyio", "CancelScope" | "fail_after" | "move_on_after"] | [ diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index 49e7826217..a6ccb34ec3 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -64,7 +64,7 @@ fn match_async_exit_stack(semantic: &SemanticModel) -> bool { for item in items { if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr { 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; } @@ -95,7 +95,7 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool { for item in items { if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr { 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; } @@ -115,7 +115,7 @@ fn is_open(checker: &mut Checker, func: &Expr) -> bool { Expr::Call(ast::ExprCall { func, .. }) => checker .semantic() .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, } } diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs index bffa54d55a..1c3cd379f2 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_namedtuple_subclass.rs @@ -74,7 +74,7 @@ pub(crate) fn no_slots_in_namedtuple_subclass( checker .semantic() .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) { checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs index c72057ea64..9cae4c6b6b 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs @@ -70,7 +70,7 @@ fn is_str_subclass(bases: &[Expr], semantic: &SemanticModel) -> bool { let mut is_str_subclass = false; for base in bases { if let Some(call_path) = semantic.resolve_call_path(base) { - match call_path.as_slice() { + match call_path.segments() { ["" | "builtins", "str"] => { is_str_subclass = true; } diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs index c0f7a0d897..78f61bf3e1 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_tuple_subclass.rs @@ -60,7 +60,7 @@ pub(crate) fn no_slots_in_tuple_subclass(checker: &mut Checker, stmt: &Stmt, cla .semantic() .resolve_call_path(map_subscript(base)) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["" | "builtins", "tuple"]) + matches!(call_path.segments(), ["" | "builtins", "tuple"]) || checker .semantic() .match_typing_call_path(&call_path, "Tuple") diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs index f1d5395b1e..9efeb95c1a 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs @@ -2,7 +2,7 @@ use ruff_python_ast::Expr; use ruff_diagnostics::{Diagnostic, 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 crate::checkers::ast::Checker; @@ -67,9 +67,9 @@ pub(crate) fn banned_attribute_access(checker: &mut Checker, expr: &Expr) { .semantic() .resolve_call_path(expr) .and_then(|call_path| { - banned_api - .iter() - .find(|(banned_path, ..)| call_path == from_qualified_name(banned_path)) + banned_api.iter().find(|(banned_path, ..)| { + call_path == CallPath::from_qualified_name(banned_path) + }) }) { checker.diagnostics.push(Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/flake8_trio/method_name.rs b/crates/ruff_linter/src/rules/flake8_trio/method_name.rs index 51a6475d7e..14a9806c6c 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/method_name.rs +++ b/crates/ruff_linter/src/rules/flake8_trio/method_name.rs @@ -71,7 +71,7 @@ impl MethodName { impl MethodName { pub(super) fn try_from(call_path: &CallPath<'_>) -> Option { - match call_path.as_slice() { + match call_path.segments() { ["trio", "CancelScope"] => Some(Self::CancelScope), ["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully), ["trio", "fail_after"] => Some(Self::FailAfter), diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs b/crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs index 475dcf05ad..95c41db491 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs @@ -64,7 +64,7 @@ pub(crate) fn unneeded_sleep(checker: &mut Checker, while_stmt: &ast::StmtWhile) if checker .semantic() .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 .diagnostics diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs b/crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs index c7b84138f7..fff7c999c5 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs +++ b/crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs @@ -63,7 +63,7 @@ pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) { if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs b/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs index 24d71fddd7..e5c329484e 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs @@ -1,7 +1,7 @@ use anyhow::Result; 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::{self as ast, Decorator, Expr}; 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| { base_classes .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| { decorators .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 .resolve_call_path(map_callable(&decorator.expression)) .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`. return semantic .resolve_call_path(map_subscript(annotation)) .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") }); } @@ -156,7 +156,7 @@ pub(crate) fn is_singledispatch_interface( semantic .resolve_call_path(&decorator.expression) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["functools", "singledispatch"]) + matches!(call_path.segments(), ["functools", "singledispatch"]) }) }) } diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs index da06a8e035..66c7f13352 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs @@ -79,7 +79,7 @@ pub(crate) fn os_sep_split(checker: &mut Checker, call: &ast::ExprCall) { if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs index f5fbdb8681..f94699e5b2 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs @@ -47,7 +47,7 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E if !checker .semantic() .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; } diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs index 175a8e6292..68c01c1d4d 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs @@ -20,7 +20,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &mut Checker, call: &ExprCall) { checker .semantic() .resolve_call_path(&call.func) - .and_then(|call_path| match call_path.as_slice() { + .and_then(|call_path| match call_path.segments() { // PTH100 ["os", "path", "abspath"] => Some(OsPathAbspath.into()), // PTH101 diff --git a/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs b/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs index 276149a49c..da3e0de4d6 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs @@ -64,7 +64,7 @@ pub(crate) fn deprecated_function(checker: &mut Checker, expr: &Expr) { checker .semantic() .resolve_call_path(expr) - .and_then(|call_path| match call_path.as_slice() { + .and_then(|call_path| match call_path.segments() { ["numpy", "round_"] => Some(("round_", "round")), ["numpy", "product"] => Some(("product", "prod")), ["numpy", "cumproduct"] => Some(("cumproduct", "cumprod")), diff --git a/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs b/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs index b25a29e46c..b8cfe827b6 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs @@ -59,13 +59,13 @@ pub(crate) fn deprecated_type_alias(checker: &mut Checker, expr: &Expr) { .resolve_call_path(expr) .and_then(|call_path| { if matches!( - call_path.as_slice(), + call_path.segments(), [ "numpy", "bool" | "int" | "float" | "complex" | "object" | "str" | "long" | "unicode" ] ) { - Some(call_path[1]) + Some(call_path.segments()[1]) } else { None } diff --git a/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs b/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs index b8ec4fa333..b51bc84e1a 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs @@ -70,7 +70,7 @@ pub(crate) fn legacy_random(checker: &mut Checker, expr: &Expr) { .and_then(|call_path| { // seeding state if matches!( - call_path.as_slice(), + call_path.segments(), [ "numpy", "random", @@ -130,7 +130,7 @@ pub(crate) fn legacy_random(checker: &mut Checker, expr: &Expr) { "zipf" ] ) { - Some(call_path[2]) + Some(call_path.segments()[2]) } else { None } diff --git a/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs b/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs index 86a98c910a..0d55e71765 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs @@ -160,7 +160,7 @@ pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) { let maybe_replacement = checker .semantic() .resolve_call_path(expr) - .and_then(|call_path| match call_path.as_slice() { + .and_then(|call_path| match call_path.segments() { // NumPy's main namespace np.* members removed in 2.0 ["numpy", "add_docstring"] => Some(Replacement { existing: "add_docstring", diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs index 656cb3e92d..4d16db06a1 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs @@ -56,7 +56,7 @@ pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() .resolve_call_path(&call.func) - .is_some_and(|call_path| !matches!(call_path.as_slice(), ["pandas", ..])) + .is_some_and(|call_path| !matches!(call_path.segments(), ["pandas", ..])) { return; } diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs index 5af88c8af1..e819582e4b 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs @@ -54,7 +54,7 @@ pub(crate) fn use_of_read_table(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() .resolve_call_path(&call.func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["pandas", "read_table"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["pandas", "read_table"])) { if let Some(Expr::StringLiteral(ast::ExprStringLiteral { value, .. })) = call .arguments diff --git a/crates/ruff_linter/src/rules/pep8_naming/helpers.rs b/crates/ruff_linter/src/rules/pep8_naming/helpers.rs index 3aff439681..b2372ed6b1 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/helpers.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/helpers.rs @@ -1,5 +1,5 @@ use itertools::Itertools; -use ruff_python_ast::call_path::collect_call_path; +use ruff_python_ast::call_path::CallPath; use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; use ruff_python_semantic::SemanticModel; @@ -32,7 +32,7 @@ pub(super) fn is_named_tuple_assignment(stmt: &Stmt, semantic: &SemanticModel) - return false; }; semantic.resolve_call_path(func).is_some_and(|call_path| { - matches!(call_path.as_slice(), ["collections", "namedtuple"]) + matches!(call_path.segments(), ["collections", "namedtuple"]) || semantic.match_typing_call_path(&call_path, "NamedTuple") }) } @@ -118,8 +118,8 @@ pub(super) fn is_django_model_import(name: &str, stmt: &Stmt, semantic: &Semanti } // Match against, e.g., `apps.get_model("zerver", "Attachment")`. - if let Some(call_path) = collect_call_path(func.as_ref()) { - if matches!(call_path.as_slice(), [.., "get_model"]) { + if let Some(call_path) = CallPath::from_expr(func.as_ref()) { + if matches!(call_path.segments(), [.., "get_model"]) { if let Some(argument) = arguments.find_argument("model_name", arguments.args.len().saturating_sub(1)) { @@ -137,7 +137,7 @@ pub(super) fn is_django_model_import(name: &str, stmt: &Stmt, semantic: &Semanti // Match against, e.g., `import_string("zerver.models.Attachment")`. if let Some(call_path) = semantic.resolve_call_path(func.as_ref()) { if matches!( - call_path.as_slice(), + call_path.segments(), ["django", "utils", "module_loading", "import_string"] ) { if let Some(argument) = arguments.find_argument("dotted_path", 0) { diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs index 5c8e2a4d1c..1e1b28378d 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -154,7 +154,7 @@ fn extract_types(annotation: &Expr, semantic: &SemanticModel) -> Option<(Vec bool { // Ex) `np.dtype(obj)` Expr::Call(ast::ExprCall { func, .. }) => semantic .resolve_call_path(func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["numpy", "dtype"])), + .is_some_and(|call_path| matches!(call_path.segments(), ["numpy", "dtype"])), // Ex) `obj.dtype` Expr::Attribute(ast::ExprAttribute { attr, .. }) => { // Ex) `obj.dtype` diff --git a/crates/ruff_linter/src/rules/pydocstyle/helpers.rs b/crates/ruff_linter/src/rules/pydocstyle/helpers.rs index c165d34fa6..4f69381e99 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/helpers.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/helpers.rs @@ -1,6 +1,6 @@ +use ruff_python_ast::call_path::CallPath; use std::collections::BTreeSet; -use ruff_python_ast::call_path::from_qualified_name; use ruff_python_ast::helpers::map_callable; use ruff_python_semantic::{Definition, SemanticModel}; use ruff_source_file::UniversalNewlines; @@ -57,7 +57,7 @@ pub(crate) fn should_ignore_definition( .is_some_and(|call_path| { ignore_decorators .iter() - .any(|decorator| from_qualified_name(decorator) == call_path) + .any(|decorator| CallPath::from_qualified_name(decorator) == call_path) }) }) } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs index 5d779ba1db..0b03a100aa 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/non_imperative_mood.rs @@ -5,7 +5,7 @@ use once_cell::sync::Lazy; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::call_path::{from_qualified_name, CallPath}; +use ruff_python_ast::call_path::CallPath; use ruff_python_semantic::analyze::visibility::{is_property, is_test}; use ruff_source_file::UniversalNewlines; use ruff_text_size::Ranged; @@ -74,7 +74,7 @@ pub(crate) fn non_imperative_mood( let property_decorators = property_decorators .iter() - .map(|decorator| from_qualified_name(decorator)) + .map(|decorator| CallPath::from_qualified_name(decorator)) .collect::>(); if is_test(&function.name) diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs index a5e7f5f325..2817705458 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_open_mode.rs @@ -91,7 +91,7 @@ fn is_open(func: &Expr, semantic: &SemanticModel) -> Option { match value.as_ref() { Expr::Call(ast::ExprCall { func, .. }) => semantic .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"])) .then_some(Kind::Pathlib), _ => None, } diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs index cbad4be65d..8e649b849e 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs @@ -51,7 +51,7 @@ pub(crate) fn invalid_envvar_default(checker: &mut Checker, call: &ast::ExprCall if checker .semantic() .resolve_call_path(&call.func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "getenv"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["os", "getenv"])) { // Find the `default` argument, if it exists. let Some(expr) = call.arguments.find_argument("default", 1) else { diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs index dfd4f6c0b4..91bdd5f44a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs @@ -44,7 +44,7 @@ pub(crate) fn invalid_envvar_value(checker: &mut Checker, call: &ast::ExprCall) if checker .semantic() .resolve_call_path(&call.func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "getenv"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["os", "getenv"])) { // Find the `key` argument, if it exists. let Some(expr) = call.arguments.find_argument("key", 0) else { diff --git a/crates/ruff_linter/src/rules/pylint/rules/logging.rs b/crates/ruff_linter/src/rules/pylint/rules/logging.rs index 823bf8bdfc..cfa3cbc6ce 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/logging.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/logging.rs @@ -118,7 +118,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 { return; }; - let ["logging", attribute] = call_path.as_slice() else { + let ["logging", attribute] = call_path.segments() else { return; }; if LoggingLevel::from_attribute(attribute).is_none() { diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs index 25929805d6..dbed3d43bb 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::call_path::{from_qualified_name, CallPath}; +use ruff_python_ast::call_path::CallPath; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::{self as ast, ParameterWithDefault}; use ruff_python_semantic::{ @@ -84,7 +84,7 @@ pub(crate) fn no_self_use( .pydocstyle .property_decorators .iter() - .map(|decorator| from_qualified_name(decorator)) + .map(|decorator| CallPath::from_qualified_name(decorator)) .collect::>(); if helpers::is_empty(body) diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs index db74a473bc..876237b04b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs @@ -67,7 +67,7 @@ pub(crate) fn non_slot_assignment(checker: &mut Checker, class_def: &ast::StmtCl checker .semantic() .resolve_call_path(base) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "object"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["", "object"])) }) { return; } diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs index df3ab96924..bf088aeff0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs @@ -203,7 +203,7 @@ fn is_allowed_value(bool_op: BoolOp, value: &Expr, semantic: &SemanticModel) -> // respected by type checkers when enforced via equality. if any_over_expr(value, &|expr| { semantic.resolve_call_path(expr).is_some_and(|call_path| { - matches!(call_path.as_slice(), ["sys", "version_info" | "platform"]) + matches!(call_path.segments(), ["sys", "version_info" | "platform"]) }) }) { return false; diff --git a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs index bdc1f01520..8a2a59c4e6 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs @@ -98,7 +98,7 @@ pub(crate) fn singledispatch_method( .semantic() .resolve_call_path(&decorator.expression) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["functools", "singledispatch"]) + matches!(call_path.segments(), ["functools", "singledispatch"]) }) { let mut diagnostic = Diagnostic::new(SingledispatchMethod, decorator.range()); diff --git a/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs b/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs index a5da0eb98d..3e82a75a7d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs @@ -58,7 +58,7 @@ pub(crate) fn subprocess_popen_preexec_fn(checker: &mut Checker, call: &ast::Exp if checker .semantic() .resolve_call_path(&call.func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "Popen"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["subprocess", "Popen"])) { if let Some(keyword) = call .arguments diff --git a/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs b/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs index 17b283bae0..c64568d2ec 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs @@ -68,7 +68,7 @@ pub(crate) fn subprocess_run_without_check(checker: &mut Checker, call: &ast::Ex if checker .semantic() .resolve_call_path(&call.func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "run"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["subprocess", "run"])) { if call.arguments.find_keyword("check").is_none() { let mut diagnostic = Diagnostic::new(SubprocessRunWithoutCheck, call.func.range()); diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs index 461a8d56c9..c72e43f585 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_list_index_lookup.rs @@ -127,7 +127,7 @@ fn enumerate_items<'a>( // Check that the function is the `enumerate` builtin. if !semantic .resolve_call_path(func.as_ref()) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["builtins" | "", "enumerate"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["builtins" | "", "enumerate"])) { return None; } diff --git a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs index e1dd828405..ae44de7828 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unspecified_encoding.rs @@ -3,7 +3,7 @@ use anyhow::Result; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; -use ruff_python_ast::call_path::{format_call_path, CallPath}; +use ruff_python_ast::call_path::CallPath; use ruff_python_ast::Expr; use ruff_text_size::{Ranged, TextRange}; @@ -74,12 +74,7 @@ pub(crate) fn unspecified_encoding(checker: &mut Checker, call: &ast::ExprCall) .semantic() .resolve_call_path(&call.func) .filter(|call_path| is_violation(call, call_path)) - .map(|call_path| { - ( - format_call_path(call_path.as_slice()), - Mode::from(&call_path), - ) - }) + .map(|call_path| (call_path.to_string(), Mode::from(&call_path))) else { return; }; @@ -164,7 +159,7 @@ fn is_violation(call: &ast::ExprCall, call_path: &CallPath) -> bool { { return false; } - match call_path.as_slice() { + match call_path.segments() { ["" | "codecs" | "_io", "open"] => { if let Some(mode_arg) = call.arguments.find_argument("mode", 1) { if is_binary_mode(mode_arg).unwrap_or(true) { @@ -176,7 +171,7 @@ fn is_violation(call: &ast::ExprCall, call_path: &CallPath) -> bool { call.arguments.find_argument("encoding", 3).is_none() } ["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => { - let mode_pos = usize::from(call_path[1] == "SpooledTemporaryFile"); + let mode_pos = usize::from(call_path.segments()[1] == "SpooledTemporaryFile"); if let Some(mode_arg) = call.arguments.find_argument("mode", mode_pos) { if is_binary_mode(mode_arg).unwrap_or(true) { // binary mode or unknown mode is no violation @@ -205,7 +200,7 @@ enum Mode { impl From<&CallPath<'_>> for Mode { fn from(value: &CallPath<'_>) -> Self { - match value.as_slice() { + match value.segments() { ["" | "codecs" | "_io", "open"] => Mode::Supported, ["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => { Mode::Supported diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs index 02066cd04b..2179278f78 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs @@ -65,7 +65,7 @@ pub(crate) fn useless_exception_statement(checker: &mut Checker, expr: &ast::Stm fn is_builtin_exception(expr: &Expr, semantic: &SemanticModel) -> bool { return semantic.resolve_call_path(expr).is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "", "SystemExit" diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs index 49bc38d1e8..e4a3fccf20 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_with_lock.rs @@ -68,7 +68,7 @@ pub(crate) fn useless_with_lock(checker: &mut Checker, with: &ast::StmtWith) { .resolve_call_path(call.func.as_ref()) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "threading", "Lock" | "RLock" | "Condition" | "Semaphore" | "BoundedSemaphore" diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs index c4325f9651..db911c1c59 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/datetime_utc_alias.rs @@ -54,7 +54,7 @@ pub(crate) fn datetime_utc_alias(checker: &mut Checker, expr: &Expr) { if checker .semantic() .resolve_call_path(expr) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["datetime", "timezone", "utc"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["datetime", "timezone", "utc"])) { let mut diagnostic = Diagnostic::new(DatetimeTimezoneUTC, expr.range()); diagnostic.try_set_fix(|| { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs index 3243b0d5b5..b87080dcd0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs @@ -7,7 +7,7 @@ use log::error; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; 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::whitespace::indentation; use ruff_python_ast::{self as ast, Stmt}; use ruff_python_codegen::Stylist; @@ -255,8 +255,8 @@ pub(crate) fn deprecated_mock_attribute(checker: &mut Checker, attribute: &ast:: return; } - if collect_call_path(&attribute.value) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["mock", "mock"])) + if CallPath::from_expr(&attribute.value) + .is_some_and(|call_path| matches!(call_path.segments(), ["mock", "mock"])) { let mut diagnostic = Diagnostic::new( DeprecatedMockImport { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs index 8a3f633114..2d11683aa1 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs @@ -468,7 +468,7 @@ pub(crate) fn f_strings( .resolve_call_path(call.func.as_ref()) .map_or(false, |call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["django", "utils", "translation", "gettext" | "gettext_lazy"] ) }) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs index 3a32210ee6..36891ecf83 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_with_maxsize_none.rs @@ -77,7 +77,7 @@ pub(crate) fn lru_cache_with_maxsize_none(checker: &mut Checker, decorator_list: && checker .semantic() .resolve_call_path(func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["functools", "lru_cache"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["functools", "lru_cache"])) { let Keyword { arg, diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs index 00c53bfc61..8bed334e11 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/lru_cache_without_parameters.rs @@ -70,7 +70,7 @@ pub(crate) fn lru_cache_without_parameters(checker: &mut Checker, decorator_list && checker .semantic() .resolve_call_path(func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["functools", "lru_cache"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["functools", "lru_cache"])) { let mut diagnostic = Diagnostic::new( LRUCacheWithoutParameters, diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs index 1809b4cea4..ae0f4eb1a4 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/open_alias.rs @@ -50,7 +50,7 @@ pub(crate) fn open_alias(checker: &mut Checker, expr: &Expr, func: &Expr) { if checker .semantic() .resolve_call_path(func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["io", "open"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["io", "open"])) { let mut diagnostic = Diagnostic::new(OpenAlias, expr.range()); if checker.semantic().is_builtin("open") { diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs index dca5dfe715..67fb737911 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/os_error_alias.rs @@ -58,7 +58,7 @@ impl AlwaysFixableViolation for OSErrorAlias { fn is_alias(expr: &Expr, semantic: &SemanticModel) -> bool { semantic.resolve_call_path(expr).is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["", "EnvironmentError" | "IOError" | "WindowsError"] | ["mmap" | "select" | "socket" | "os", "error"] ) @@ -69,7 +69,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel) -> bool { fn is_os_error(expr: &Expr, semantic: &SemanticModel) -> bool { semantic .resolve_call_path(expr) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "OSError"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["", "OSError"])) } /// Create a [`Diagnostic`] for a single target, like an [`Expr::Name`]. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs index af49f8a202..de4e8df6d0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs @@ -98,7 +98,7 @@ pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) { if !checker .semantic() .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"])) { continue; } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs index 6efb82bd4b..8005d4f691 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs @@ -61,7 +61,7 @@ pub(crate) fn replace_stdout_stderr(checker: &mut Checker, call: &ast::ExprCall) if checker .semantic() .resolve_call_path(&call.func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "run"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["subprocess", "run"])) { // Find `stdout` and `stderr` kwargs. let Some(stdout) = call.arguments.find_keyword("stdout") else { @@ -75,11 +75,11 @@ pub(crate) fn replace_stdout_stderr(checker: &mut Checker, call: &ast::ExprCall) if !checker .semantic() .resolve_call_path(&stdout.value) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "PIPE"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["subprocess", "PIPE"])) || !checker .semantic() .resolve_call_path(&stderr.value) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "PIPE"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["subprocess", "PIPE"])) { return; } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs index 08cba063d6..96e5edf7f6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs @@ -57,7 +57,7 @@ pub(crate) fn replace_universal_newlines(checker: &mut Checker, call: &ast::Expr if checker .semantic() .resolve_call_path(&call.func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "run"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["subprocess", "run"])) { let Some(kwarg) = call.arguments.find_keyword("universal_newlines") else { return; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs index 43ffafa3d5..6e177dd461 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs @@ -62,7 +62,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel, target_version: PythonVersion semantic.resolve_call_path(expr).is_some_and(|call_path| { if target_version >= PythonVersion::Py311 { matches!( - call_path.as_slice(), + call_path.segments(), ["socket", "timeout"] | ["asyncio", "TimeoutError"] ) } else { @@ -74,7 +74,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel, target_version: PythonVersion target_version >= PythonVersion::Py310, "lint should only be used for Python 3.10+", ); - matches!(call_path.as_slice(), ["socket", "timeout"]) + matches!(call_path.segments(), ["socket", "timeout"]) } }) } @@ -83,7 +83,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel, target_version: PythonVersion fn is_timeout_error(expr: &Expr, semantic: &SemanticModel) -> bool { semantic .resolve_call_path(expr) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "TimeoutError"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["", "TimeoutError"])) } /// Create a [`Diagnostic`] for a single target, like an [`Expr::Name`]. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs index 1486b6a28b..072145caa0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/type_of_primitive.rs @@ -61,7 +61,7 @@ pub(crate) fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, if !checker .semantic() .resolve_call_path(func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "type"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["", "type"])) { return; } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs index ceebbb069e..e939dd9eb9 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs @@ -54,7 +54,7 @@ pub(crate) fn typing_text_str_alias(checker: &mut Checker, expr: &Expr) { if checker .semantic() .resolve_call_path(expr) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["typing", "Text"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["typing", "Text"])) { let mut diagnostic = Diagnostic::new(TypingTextStrAlias, expr.range()); if checker.semantic().is_builtin("str") { diff --git a/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs b/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs index 54405ff173..838c884f77 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/bit_count.rs @@ -101,7 +101,7 @@ pub(crate) fn bit_count(checker: &mut Checker, call: &ExprCall) { if !checker .semantic() .resolve_call_path(func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["" | "builtins", "bin"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["" | "builtins", "bin"])) { return; } diff --git a/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs b/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs index b8c7adf7cc..12c23fa2c3 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/hashlib_digest_hex.rs @@ -83,10 +83,12 @@ pub(crate) fn hashlib_digest_hex(checker: &mut Checker, call: &ExprCall) { return; }; - if checker.semantic().resolve_call_path(func).is_some_and( - |call_path: smallvec::SmallVec<[&str; 8]>| { + if checker + .semantic() + .resolve_call_path(func) + .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "hashlib", "md5" @@ -106,8 +108,8 @@ pub(crate) fn hashlib_digest_hex(checker: &mut Checker, call: &ExprCall) { | "_Hash" ] ) - }, - ) { + }) + { let mut diagnostic = Diagnostic::new(HashlibDigestHex, call.range()); if arguments.is_empty() { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( diff --git a/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs b/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs index a9fb853ba6..2e9e1a4159 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/implicit_cwd.rs @@ -77,7 +77,7 @@ pub(crate) fn no_implicit_cwd(checker: &mut Checker, call: &ExprCall) { if !checker .semantic() .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"])) { return; } diff --git a/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs b/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs index c5700635c9..596316abcc 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs @@ -64,7 +64,7 @@ pub(crate) fn metaclass_abcmeta(checker: &mut Checker, class_def: &StmtClassDef) if !checker .semantic() .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"])) { return; } diff --git a/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs b/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs index 4789bf57a9..5c5d1be7ee 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs @@ -74,7 +74,7 @@ pub(crate) fn print_empty_string(checker: &mut Checker, call: &ast::ExprCall) { .semantic() .resolve_call_path(&call.func) .as_ref() - .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "print"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["", "print"])) { return; } diff --git a/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs b/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs index 3768bfc130..9249b8c36d 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/redundant_log_base.rs @@ -78,7 +78,7 @@ pub(crate) fn redundant_log_base(checker: &mut Checker, call: &ast::ExprCall) { .semantic() .resolve_call_path(&call.func) .as_ref() - .is_some_and(|call_path| matches!(call_path.as_slice(), ["math", "log"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["math", "log"])) { return; } @@ -91,7 +91,7 @@ pub(crate) fn redundant_log_base(checker: &mut Checker, call: &ast::ExprCall) { .semantic() .resolve_call_path(base) .as_ref() - .is_some_and(|call_path| matches!(call_path.as_slice(), ["math", "e"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["math", "e"])) { Base::E } else { diff --git a/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs b/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs index ff36b06476..ab691ab78f 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs @@ -61,7 +61,7 @@ pub(crate) fn regex_flag_alias(checker: &mut Checker, expr: &Expr) { checker .semantic() .resolve_call_path(expr) - .and_then(|call_path| match call_path.as_slice() { + .and_then(|call_path| match call_path.segments() { ["re", "A"] => Some(RegexFlag::Ascii), ["re", "I"] => Some(RegexFlag::IgnoreCase), ["re", "L"] => Some(RegexFlag::Locale), diff --git a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs index 1139ae7e12..58f2dbfeed 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs @@ -74,7 +74,7 @@ pub(crate) fn asyncio_dangling_task(expr: &Expr, semantic: &SemanticModel) -> Op if let Some(method) = semantic .resolve_call_path(func) - .and_then(|call_path| match call_path.as_slice() { + .and_then(|call_path| match call_path.segments() { ["asyncio", "create_task"] => Some(Method::CreateTask), ["asyncio", "ensure_future"] => Some(Method::EnsureFuture), _ => None, @@ -95,7 +95,7 @@ pub(crate) fn asyncio_dangling_task(expr: &Expr, semantic: &SemanticModel) -> Op if let Expr::Name(name) = value.as_ref() { if typing::resolve_assignment(value, semantic).is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "asyncio", "get_event_loop" | "get_running_loop" | "new_event_loop" diff --git a/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs b/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs index 028aca8579..c4324792dc 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs @@ -78,7 +78,7 @@ pub(crate) fn default_factory_kwarg(checker: &mut Checker, call: &ast::ExprCall) if !checker .semantic() .resolve_call_path(call.func.as_ref()) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["collections", "defaultdict"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["collections", "defaultdict"])) { return; } diff --git a/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs b/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs index 5e23bcb9ab..7bf343b695 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/function_call_in_dataclass_default.rs @@ -3,7 +3,7 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::call_path::compose_call_path; -use ruff_python_ast::call_path::{from_qualified_name, CallPath}; +use ruff_python_ast::call_path::CallPath; use ruff_python_semantic::analyze::typing::is_immutable_func; use ruff_text_size::Ranged; @@ -85,7 +85,7 @@ pub(crate) fn function_call_in_dataclass_default( .flake8_bugbear .extend_immutable_calls .iter() - .map(|target| from_qualified_name(target)) + .map(|target| CallPath::from_qualified_name(target)) .collect(); for statement in &class_def.body { diff --git a/crates/ruff_linter/src/rules/ruff/rules/helpers.rs b/crates/ruff_linter/src/rules/ruff/rules/helpers.rs index 5aba7b8e0d..7025ab3076 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/helpers.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/helpers.rs @@ -26,7 +26,7 @@ pub(super) fn is_dataclass_field(func: &Expr, semantic: &SemanticModel) -> bool semantic .resolve_call_path(func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["dataclasses", "field"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["dataclasses", "field"])) } /// Returns `true` if the given [`Expr`] is a `typing.ClassVar` annotation. @@ -60,7 +60,7 @@ pub(super) fn is_dataclass(class_def: &ast::StmtClassDef, semantic: &SemanticMod class_def.decorator_list.iter().any(|decorator| { semantic .resolve_call_path(map_callable(&decorator.expression)) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["dataclasses", "dataclass"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["dataclasses", "dataclass"])) }) } @@ -74,7 +74,7 @@ pub(super) fn has_default_copy_semantics( ) -> bool { analyze::class::any_call_path(class_def, semantic, &|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["pydantic", "BaseModel" | "BaseSettings" | "BaseConfig"] | ["pydantic_settings", "BaseSettings"] | ["msgspec", "Struct"] diff --git a/crates/ruff_linter/src/rules/ruff/typing.rs b/crates/ruff_linter/src/rules/ruff/typing.rs index 1c176ca7e4..deeb339f0c 100644 --- a/crates/ruff_linter/src/rules/ruff/typing.rs +++ b/crates/ruff_linter/src/rules/ruff/typing.rs @@ -12,7 +12,7 @@ use ruff_source_file::Locator; /// A known type is either a builtin type, any object from the standard library, /// or a type from the `typing_extensions` module. fn is_known_type(call_path: &CallPath, minor_version: u8) -> bool { - match call_path.as_slice() { + match call_path.segments() { ["" | "typing_extensions", ..] => true, [module, ..] => is_known_standard_library(minor_version, module), _ => false, @@ -122,10 +122,10 @@ impl<'a> TypingTarget<'a> { |call_path| { if semantic.match_typing_call_path(&call_path, "Any") { Some(TypingTarget::Any) - } else if matches!(call_path.as_slice(), ["" | "builtins", "object"]) { + } else if matches!(call_path.segments(), ["" | "builtins", "object"]) { Some(TypingTarget::Object) } else if semantic.match_typing_call_path(&call_path, "Hashable") - || matches!(call_path.as_slice(), ["collections", "abc", "Hashable"]) + || matches!(call_path.segments(), ["collections", "abc", "Hashable"]) { Some(TypingTarget::Hashable) } else if !is_known_type(&call_path, minor_version) { diff --git a/crates/ruff_linter/src/rules/tryceratops/helpers.rs b/crates/ruff_linter/src/rules/tryceratops/helpers.rs index bd12ddebfe..5889a1324a 100644 --- a/crates/ruff_linter/src/rules/tryceratops/helpers.rs +++ b/crates/ruff_linter/src/rules/tryceratops/helpers.rs @@ -36,7 +36,7 @@ impl<'a, 'b> Visitor<'b> for LoggerCandidateVisitor<'a, 'b> { } Expr::Name(_) => { if let Some(call_path) = self.semantic.resolve_call_path(call.func.as_ref()) { - if let ["logging", attribute] = call_path.as_slice() { + if let ["logging", attribute] = call_path.segments() { if let Some(logging_level) = LoggingLevel::from_attribute(attribute) { { self.calls.push((call, logging_level)); diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs b/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs index de5dd2c47c..474947402a 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/error_instead_of_exception.rs @@ -92,7 +92,7 @@ pub(crate) fn error_instead_of_exception(checker: &mut Checker, handlers: &[Exce .semantic() .resolve_call_path(expr.func.as_ref()) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["logging", "error"]) + matches!(call_path.segments(), ["logging", "error"]) }) { Applicability::Safe @@ -119,7 +119,7 @@ pub(crate) fn error_instead_of_exception(checker: &mut Checker, handlers: &[Exce .semantic() .resolve_call_path(expr.func.as_ref()) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["logging", "error"]) + matches!(call_path.segments(), ["logging", "error"]) }) { Applicability::Safe diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs index b50d6fd7e7..1222e881ff 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_args.rs @@ -73,7 +73,7 @@ pub(crate) fn raise_vanilla_args(checker: &mut Checker, expr: &Expr) { if checker .semantic() .resolve_call_path(func) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "NotImplementedError"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["", "NotImplementedError"])) { return; } diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs index 0c1b742ba5..6ca4d8d379 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/raise_vanilla_class.rs @@ -70,7 +70,7 @@ pub(crate) fn raise_vanilla_class(checker: &mut Checker, expr: &Expr) { } else { expr }) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "Exception"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["", "Exception"])) { checker .diagnostics diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs b/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs index 3c1ba73451..59a3f5ecd1 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/raise_within_try.rs @@ -116,7 +116,7 @@ pub(crate) fn raise_within_try(checker: &mut Checker, body: &[Stmt], handlers: & .semantic() .resolve_call_path(expr) .is_some_and(|call_path| { - matches!(call_path.as_slice(), ["", "Exception" | "BaseException"]) + matches!(call_path.segments(), ["", "Exception" | "BaseException"]) }) }) { diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs b/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs index eaa8c5e749..ec0a2c149c 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs @@ -81,7 +81,7 @@ fn check_type_check_call(checker: &mut Checker, call: &Expr) -> bool { .resolve_call_path(call) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["", "isinstance" | "issubclass" | "callable"] ) }) @@ -106,7 +106,7 @@ fn is_builtin_exception(checker: &mut Checker, exc: &Expr) -> bool { .resolve_call_path(exc) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "", "ArithmeticError" diff --git a/crates/ruff_python_ast/src/call_path.rs b/crates/ruff_python_ast/src/call_path.rs index fe8043a106..0ad5aad711 100644 --- a/crates/ruff_python_ast/src/call_path.rs +++ b/crates/ruff_python_ast/src/call_path.rs @@ -1,19 +1,154 @@ -use smallvec::{smallvec, SmallVec}; +use smallvec::SmallVec; +use std::fmt::{Display, Formatter, Write}; use crate::{nodes, Expr}; /// A representation of a qualified name, like `typing.List`. -pub type CallPath<'a> = SmallVec<[&'a str; 8]>; +#[derive(Debug, Clone, Eq, Hash)] +pub struct CallPath<'a> { + segments: SmallVec<[&'a str; 8]>, +} + +impl<'a> CallPath<'a> { + pub fn from_expr(expr: &'a Expr) -> Option { + let segments = collect_call_path(expr)?; + Some(Self { segments }) + } + + /// Create a [`CallPath`] from an unqualified name. + /// + /// ```rust + /// # use smallvec::smallvec; + /// # use ruff_python_ast::call_path::CallPath; + /// + /// assert_eq!(CallPath::from_unqualified_name("typing.List").segments(), ["typing", "List"]); + /// assert_eq!(CallPath::from_unqualified_name("list").segments(), ["list"]); + /// ``` + #[inline] + pub fn from_unqualified_name(name: &'a str) -> Self { + name.split('.').collect() + } + + /// Create a [`CallPath`] from a fully-qualified name. + /// + /// ```rust + /// # use smallvec::smallvec; + /// # use ruff_python_ast::call_path::CallPath; + /// + /// assert_eq!(CallPath::from_qualified_name("typing.List").segments(), ["typing", "List"]); + /// assert_eq!(CallPath::from_qualified_name("list").segments(), ["", "list"]); + /// ``` + #[inline] + pub fn from_qualified_name(name: &'a str) -> Self { + if let Some(dot) = name.find('.') { + let mut segments = SmallVec::new(); + segments.push(&name[..dot]); + segments.extend(name[dot + 1..].split('.')); + Self { segments } + } else { + // Special-case: for builtins, return `["", "int"]` instead of `["int"]`. + Self::from_slice(&["", name]) + } + } + + #[inline] + pub fn from_slice(segments: &[&'a str]) -> Self { + Self { + segments: segments.into(), + } + } + + pub fn starts_with(&self, other: &CallPath) -> bool { + self.segments().starts_with(other.segments()) + } + + #[inline] + pub fn segments(&self) -> &[&'a str] { + &self.segments + } + + #[inline] + pub fn into_boxed_slice(self) -> Box<[&'a str]> { + self.segments.into_boxed_slice() + } +} + +impl<'a> FromIterator<&'a str> for CallPath<'a> { + fn from_iter>(iter: I) -> Self { + Self { + segments: iter.into_iter().collect(), + } + } +} + +impl<'a, 'b> PartialEq> for CallPath<'a> { + #[inline] + fn eq(&self, other: &CallPath<'b>) -> bool { + self.segments == other.segments + } +} + +#[derive(Debug, Clone, Default)] +pub struct CallPathBuilder<'a> { + segments: SmallVec<[&'a str; 8]>, +} + +impl<'a> CallPathBuilder<'a> { + pub fn with_capacity(capacity: usize) -> Self { + Self { + segments: SmallVec::with_capacity(capacity), + } + } + + pub fn new() -> Self { + Self::default() + } + + pub fn from_path(call_path: CallPath<'a>) -> Self { + Self { + segments: call_path.segments, + } + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.segments.is_empty() + } + + #[inline] + pub fn push(&mut self, segment: &'a str) { + self.segments.push(segment); + } + + pub fn pop(&mut self) { + self.segments.pop(); + } + + #[inline] + pub fn extend(&mut self, segments: impl IntoIterator) { + self.segments.extend(segments); + } + + pub fn extend_from_slice(&mut self, segments: &[&'a str]) { + self.segments.extend_from_slice(segments); + } + + pub fn build(self) -> CallPath<'a> { + CallPath { + segments: self.segments, + } + } +} /// Convert an `Expr` to its [`CallPath`] segments (like `["typing", "List"]`). -pub fn collect_call_path(expr: &Expr) -> Option { +fn collect_call_path(expr: &Expr) -> Option> { // Unroll the loop up to eight times, to match the maximum number of expected attributes. // In practice, unrolling appears to give about a 4x speed-up on this hot path. let attr1 = match expr { Expr::Attribute(attr1) => attr1, // Ex) `foo` Expr::Name(nodes::ExprName { id, .. }) => { - return Some(CallPath::from_slice(&[id.as_str()])) + return Some(SmallVec::from_slice(&[id.as_str()])) } _ => return None, }; @@ -22,7 +157,7 @@ pub fn collect_call_path(expr: &Expr) -> Option { Expr::Attribute(attr2) => attr2, // Ex) `foo.bar` Expr::Name(nodes::ExprName { id, .. }) => { - return Some(CallPath::from_slice(&[id.as_str(), attr1.attr.as_str()])) + return Some(SmallVec::from_slice(&[id.as_str(), attr1.attr.as_str()])) } _ => return None, }; @@ -31,7 +166,7 @@ pub fn collect_call_path(expr: &Expr) -> Option { Expr::Attribute(attr3) => attr3, // Ex) `foo.bar.baz` Expr::Name(nodes::ExprName { id, .. }) => { - return Some(CallPath::from_slice(&[ + return Some(SmallVec::from_slice(&[ id.as_str(), attr2.attr.as_str(), attr1.attr.as_str(), @@ -44,7 +179,7 @@ pub fn collect_call_path(expr: &Expr) -> Option { Expr::Attribute(attr4) => attr4, // Ex) `foo.bar.baz.bop` Expr::Name(nodes::ExprName { id, .. }) => { - return Some(CallPath::from_slice(&[ + return Some(SmallVec::from_slice(&[ id.as_str(), attr3.attr.as_str(), attr2.attr.as_str(), @@ -58,7 +193,7 @@ pub fn collect_call_path(expr: &Expr) -> Option { Expr::Attribute(attr5) => attr5, // Ex) `foo.bar.baz.bop.bap` Expr::Name(nodes::ExprName { id, .. }) => { - return Some(CallPath::from_slice(&[ + return Some(SmallVec::from_slice(&[ id.as_str(), attr4.attr.as_str(), attr3.attr.as_str(), @@ -73,7 +208,7 @@ pub fn collect_call_path(expr: &Expr) -> Option { Expr::Attribute(attr6) => attr6, // Ex) `foo.bar.baz.bop.bap.bab` Expr::Name(nodes::ExprName { id, .. }) => { - return Some(CallPath::from_slice(&[ + return Some(SmallVec::from_slice(&[ id.as_str(), attr5.attr.as_str(), attr4.attr.as_str(), @@ -89,7 +224,7 @@ pub fn collect_call_path(expr: &Expr) -> Option { Expr::Attribute(attr7) => attr7, // Ex) `foo.bar.baz.bop.bap.bab.bob` Expr::Name(nodes::ExprName { id, .. }) => { - return Some(CallPath::from_slice(&[ + return Some(SmallVec::from_slice(&[ id.as_str(), attr6.attr.as_str(), attr5.attr.as_str(), @@ -106,7 +241,7 @@ pub fn collect_call_path(expr: &Expr) -> Option { Expr::Attribute(attr8) => attr8, // Ex) `foo.bar.baz.bop.bap.bab.bob.bib` Expr::Name(nodes::ExprName { id, .. }) => { - return Some(CallPath::from_slice(&[ + return Some(SmallVec::from([ id.as_str(), attr7.attr.as_str(), attr6.attr.as_str(), @@ -135,70 +270,59 @@ pub fn collect_call_path(expr: &Expr) -> Option { }) } -/// Convert an `Expr` to its call path (like `List`, or `typing.List`). -pub fn compose_call_path(expr: &Expr) -> Option { - collect_call_path(expr).map(|call_path| format_call_path(&call_path)) +impl Display for CallPath<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + format_call_path_segments(self.segments(), f) + } } -/// Format a call path for display. -pub fn format_call_path(call_path: &[&str]) -> String { - if call_path.first().map_or(false, |first| first.is_empty()) { +/// Convert an `Expr` to its call path (like `List`, or `typing.List`). +pub fn compose_call_path(expr: &Expr) -> Option { + CallPath::from_expr(expr).map(|call_path| call_path.to_string()) +} + +pub fn format_call_path_segments(segments: &[&str], w: &mut dyn Write) -> std::fmt::Result { + if segments.first().is_some_and(|first| first.is_empty()) { // If the first segment is empty, the `CallPath` is that of a builtin. // Ex) `["", "bool"]` -> `"bool"` - call_path[1..].join(".") - } else if call_path - .first() - .map_or(false, |first| matches!(*first, ".")) - { + let mut first = true; + + for segment in segments.iter().skip(1) { + if !first { + w.write_char('.')?; + } + + w.write_str(segment)?; + first = false; + } + } else if segments.first().is_some_and(|first| matches!(*first, ".")) { // If the call path is dot-prefixed, it's an unresolved relative import. // Ex) `[".foo", "bar"]` -> `".foo.bar"` - let mut formatted = String::new(); - let mut iter = call_path.iter(); + + let mut iter = segments.iter(); for segment in iter.by_ref() { if *segment == "." { - formatted.push('.'); + w.write_char('.')?; } else { - formatted.push_str(segment); + w.write_str(segment)?; break; } } for segment in iter { - formatted.push('.'); - formatted.push_str(segment); + w.write_char('.')?; + w.write_str(segment)?; } - formatted } else { - call_path.join(".") - } -} + let mut first = true; + for segment in segments { + if !first { + w.write_char('.')?; + } -/// Create a [`CallPath`] from an unqualified name. -/// -/// ```rust -/// # use smallvec::smallvec; -/// # use ruff_python_ast::call_path::from_unqualified_name; -/// -/// assert_eq!(from_unqualified_name("typing.List").as_slice(), ["typing", "List"]); -/// assert_eq!(from_unqualified_name("list").as_slice(), ["list"]); -/// ``` -pub fn from_unqualified_name(name: &str) -> CallPath { - name.split('.').collect() -} - -/// Create a [`CallPath`] from a fully-qualified name. -/// -/// ```rust -/// # use smallvec::smallvec; -/// # use ruff_python_ast::call_path::from_qualified_name; -/// -/// assert_eq!(from_qualified_name("typing.List").as_slice(), ["typing", "List"]); -/// assert_eq!(from_qualified_name("list").as_slice(), ["", "list"]); -/// ``` -pub fn from_qualified_name(name: &str) -> CallPath { - if name.contains('.') { - name.split('.').collect() - } else { - // Special-case: for builtins, return `["", "int"]` instead of `["int"]`. - smallvec!["", name] + w.write_str(segment)?; + first = false; + } } + + Ok(()) } diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 174be5f987..db928fec14 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -2,13 +2,12 @@ use std::borrow::Cow; use std::path::Path; use rustc_hash::FxHashMap; -use smallvec::SmallVec; use ruff_python_trivia::{indentation_at_offset, CommentRanges, SimpleTokenKind, SimpleTokenizer}; use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; -use crate::call_path::CallPath; +use crate::call_path::{CallPath, CallPathBuilder}; use crate::parenthesize::parenthesized_range; use crate::statement_visitor::StatementVisitor; use crate::visitor::Visitor; @@ -793,16 +792,16 @@ pub fn to_module_path(package: &Path, path: &Path) -> Option> { /// ```rust /// # use ruff_python_ast::helpers::collect_import_from_member; /// -/// assert_eq!(collect_import_from_member(None, None, "bar").as_slice(), ["bar"]); -/// assert_eq!(collect_import_from_member(Some(1), None, "bar").as_slice(), [".", "bar"]); -/// assert_eq!(collect_import_from_member(Some(1), Some("foo"), "bar").as_slice(), [".", "foo", "bar"]); +/// assert_eq!(collect_import_from_member(None, None, "bar").segments(), ["bar"]); +/// assert_eq!(collect_import_from_member(Some(1), None, "bar").segments(), [".", "bar"]); +/// assert_eq!(collect_import_from_member(Some(1), Some("foo"), "bar").segments(), [".", "foo", "bar"]); /// ``` pub fn collect_import_from_member<'a>( level: Option, module: Option<&'a str>, member: &'a str, ) -> CallPath<'a> { - let mut call_path: CallPath = SmallVec::with_capacity( + let mut call_path_builder = CallPathBuilder::with_capacity( level.unwrap_or_default() as usize + module .map(|module| module.split('.').count()) @@ -814,20 +813,20 @@ pub fn collect_import_from_member<'a>( if let Some(level) = level { if level > 0 { for _ in 0..level { - call_path.push("."); + call_path_builder.push("."); } } } // Add the remaining segments. if let Some(module) = module { - call_path.extend(module.split('.')); + call_path_builder.extend(module.split('.')); } // Add the member. - call_path.push(member); + call_path_builder.push(member); - call_path + call_path_builder.build() } /// Format the call path for a relative import, or `None` if the relative import extends beyond @@ -840,27 +839,28 @@ pub fn from_relative_import<'a>( // The remaining segments to the call path (e.g., given `bar.baz`, `["baz"]`). tail: &[&'a str], ) -> Option> { - let mut call_path: CallPath = SmallVec::with_capacity(module.len() + import.len() + tail.len()); + let mut call_path_builder = + CallPathBuilder::with_capacity(module.len() + import.len() + tail.len()); // Start with the module path. - call_path.extend(module.iter().map(String::as_str)); + call_path_builder.extend(module.iter().map(String::as_str)); // Remove segments based on the number of dots. for segment in import { if *segment == "." { - if call_path.is_empty() { + if call_path_builder.is_empty() { return None; } - call_path.pop(); + call_path_builder.pop(); } else { - call_path.push(segment); + call_path_builder.push(segment); } } // Add the remaining segments. - call_path.extend_from_slice(tail); + call_path_builder.extend_from_slice(tail); - Some(call_path) + Some(call_path_builder.build()) } /// Given an imported module (based on its relative import level and module name), return the diff --git a/crates/ruff_python_semantic/src/analyze/function_type.rs b/crates/ruff_python_semantic/src/analyze/function_type.rs index 4dca8221e0..e3c2ba768d 100644 --- a/crates/ruff_python_semantic/src/analyze/function_type.rs +++ b/crates/ruff_python_semantic/src/analyze/function_type.rs @@ -1,5 +1,4 @@ -use ruff_python_ast::call_path::collect_call_path; -use ruff_python_ast::call_path::from_qualified_name; +use ruff_python_ast::call_path::CallPath; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::Decorator; @@ -38,7 +37,7 @@ pub fn classify( semantic .resolve_call_path(map_callable(expr)) .is_some_and( |call_path| { - matches!(call_path.as_slice(), ["", "type"] | ["abc", "ABCMeta"]) + matches!(call_path.segments(), ["", "type"] | ["abc", "ABCMeta"]) }) }) || decorator_list.iter().any(|decorator| is_class_method(decorator, semantic, classmethod_decorators)) @@ -63,11 +62,11 @@ fn is_static_method( .resolve_call_path(decorator) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["", "staticmethod"] | ["abc", "abstractstaticmethod"] ) || staticmethod_decorators .iter() - .any(|decorator| call_path == from_qualified_name(decorator)) + .any(|decorator| call_path == CallPath::from_qualified_name(decorator)) }) { return true; @@ -76,8 +75,8 @@ fn is_static_method( // We do not have a resolvable call path, most likely from a decorator like // `@someproperty.setter`. Instead, match on the last element. if !staticmethod_decorators.is_empty() { - if collect_call_path(decorator).is_some_and(|call_path| { - call_path.last().is_some_and(|tail| { + if CallPath::from_expr(decorator).is_some_and(|call_path| { + call_path.segments().last().is_some_and(|tail| { staticmethod_decorators .iter() .any(|decorator| tail == decorator) @@ -103,11 +102,11 @@ fn is_class_method( .resolve_call_path(decorator) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["", "classmethod"] | ["abc", "abstractclassmethod"] ) || classmethod_decorators .iter() - .any(|decorator| call_path == from_qualified_name(decorator)) + .any(|decorator| call_path == CallPath::from_qualified_name(decorator)) }) { return true; @@ -116,8 +115,8 @@ fn is_class_method( // We do not have a resolvable call path, most likely from a decorator like // `@someproperty.setter`. Instead, match on the last element. if !classmethod_decorators.is_empty() { - if collect_call_path(decorator).is_some_and(|call_path| { - call_path.last().is_some_and(|tail| { + if CallPath::from_expr(decorator).is_some_and(|call_path| { + call_path.segments().last().is_some_and(|tail| { classmethod_decorators .iter() .any(|decorator| tail == decorator) diff --git a/crates/ruff_python_semantic/src/analyze/imports.rs b/crates/ruff_python_semantic/src/analyze/imports.rs index 04aba5e9f6..10ea0d927b 100644 --- a/crates/ruff_python_semantic/src/analyze/imports.rs +++ b/crates/ruff_python_semantic/src/analyze/imports.rs @@ -20,7 +20,7 @@ pub fn is_sys_path_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool { .resolve_call_path(func.as_ref()) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "sys", "path", @@ -50,7 +50,7 @@ pub fn is_os_environ_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool .resolve_call_path(func.as_ref()) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["os", "putenv" | "unsetenv"] | [ "os", @@ -64,19 +64,19 @@ pub fn is_os_environ_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool Stmt::Delete(ast::StmtDelete { targets, .. }) => targets.iter().any(|target| { semantic .resolve_call_path(map_subscript(target)) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "environ"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["os", "environ"])) }), Stmt::Assign(ast::StmtAssign { targets, .. }) => targets.iter().any(|target| { semantic .resolve_call_path(map_subscript(target)) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "environ"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["os", "environ"])) }), Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => semantic .resolve_call_path(map_subscript(target)) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "environ"])), + .is_some_and(|call_path| matches!(call_path.segments(), ["os", "environ"])), Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => semantic .resolve_call_path(map_subscript(target)) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "environ"])), + .is_some_and(|call_path| matches!(call_path.segments(), ["os", "environ"])), _ => false, } } @@ -96,5 +96,5 @@ pub fn is_matplotlib_activation(stmt: &Stmt, semantic: &SemanticModel) -> bool { }; semantic .resolve_call_path(func.as_ref()) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["matplotlib", "use"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["matplotlib", "use"])) } diff --git a/crates/ruff_python_semantic/src/analyze/logging.rs b/crates/ruff_python_semantic/src/analyze/logging.rs index f4ab056f05..cc90f4107c 100644 --- a/crates/ruff_python_semantic/src/analyze/logging.rs +++ b/crates/ruff_python_semantic/src/analyze/logging.rs @@ -1,4 +1,4 @@ -use ruff_python_ast::call_path::{collect_call_path, from_qualified_name}; +use ruff_python_ast::call_path::CallPath; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::{self as ast, Arguments, Expr, Keyword}; @@ -29,7 +29,7 @@ pub fn is_logger_candidate( // logger object, the `logging` module itself, or `flask.current_app.logger`. if let Some(call_path) = semantic.resolve_call_path(value) { if matches!( - call_path.as_slice(), + call_path.segments(), ["logging"] | ["flask", "current_app", "logger"] ) { return true; @@ -37,7 +37,7 @@ pub fn is_logger_candidate( if logger_objects .iter() - .any(|logger| from_qualified_name(logger) == call_path) + .any(|logger| CallPath::from_qualified_name(logger) == call_path) { return true; } @@ -47,8 +47,8 @@ pub fn is_logger_candidate( // Otherwise, if the symbol was defined in the current module, match against some common // logger names. - if let Some(call_path) = collect_call_path(value) { - if let Some(tail) = call_path.last() { + if let Some(call_path) = CallPath::from_expr(value) { + if let Some(tail) = call_path.segments().last() { if tail.starts_with("log") || tail.ends_with("logger") || tail.ends_with("logging") @@ -79,7 +79,7 @@ pub fn exc_info<'a>(arguments: &'a Arguments, semantic: &SemanticModel) -> Optio .value .as_call_expr() .and_then(|call| semantic.resolve_call_path(&call.func)) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["sys", "exc_info"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["sys", "exc_info"])) { return Some(exc_info); } diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index a625110da2..1664ba50cf 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -1,6 +1,6 @@ //! Analysis rules for the `typing` module. -use ruff_python_ast::call_path::{from_qualified_name, from_unqualified_name, CallPath}; +use ruff_python_ast::call_path::CallPath; use ruff_python_ast::helpers::{any_over_expr, is_const_false, map_subscript}; use ruff_python_ast::{self as ast, Expr, Int, Operator, ParameterWithDefault, Parameters, Stmt}; use ruff_python_stdlib::typing::{ @@ -43,27 +43,27 @@ pub fn match_annotated_subscript<'a>( extend_generics: &[String], ) -> Option { semantic.resolve_call_path(expr).and_then(|call_path| { - if is_standard_library_literal(call_path.as_slice()) { + if is_standard_library_literal(call_path.segments()) { return Some(SubscriptKind::Literal); } - if is_standard_library_generic(call_path.as_slice()) + if is_standard_library_generic(call_path.segments()) || extend_generics .iter() - .map(|target| from_qualified_name(target)) + .map(|target| CallPath::from_qualified_name(target)) .any(|target| call_path == target) { return Some(SubscriptKind::Generic); } - if is_pep_593_generic_type(call_path.as_slice()) { + if is_pep_593_generic_type(call_path.segments()) { return Some(SubscriptKind::PEP593Annotation); } for module in typing_modules { - let module_call_path: CallPath = from_unqualified_name(module); + let module_call_path: CallPath = CallPath::from_unqualified_name(module); if call_path.starts_with(&module_call_path) { - if let Some(member) = call_path.last() { + if let Some(member) = call_path.segments().last() { if is_literal_member(member) { return Some(SubscriptKind::Literal); } @@ -106,7 +106,7 @@ pub fn to_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> Option Option bool { semantic.resolve_call_path(expr).is_some_and(|call_path| { - let [module, name] = call_path.as_slice() else { + let [module, name] = call_path.segments() else { return false; }; has_pep_585_generic(module, name) @@ -218,8 +218,8 @@ pub fn is_immutable_annotation( match expr { Expr::Name(_) | Expr::Attribute(_) => { semantic.resolve_call_path(expr).is_some_and(|call_path| { - is_immutable_non_generic_type(call_path.as_slice()) - || is_immutable_generic_type(call_path.as_slice()) + is_immutable_non_generic_type(call_path.segments()) + || is_immutable_generic_type(call_path.segments()) || extend_immutable_calls .iter() .any(|target| call_path == *target) @@ -227,9 +227,9 @@ pub fn is_immutable_annotation( } Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => { semantic.resolve_call_path(value).is_some_and(|call_path| { - if is_immutable_generic_type(call_path.as_slice()) { + if is_immutable_generic_type(call_path.segments()) { true - } else if matches!(call_path.as_slice(), ["typing", "Union"]) { + } else if matches!(call_path.segments(), ["typing", "Union"]) { if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() { elts.iter().all(|elt| { is_immutable_annotation(elt, semantic, extend_immutable_calls) @@ -237,9 +237,9 @@ pub fn is_immutable_annotation( } else { false } - } else if matches!(call_path.as_slice(), ["typing", "Optional"]) { + } else if matches!(call_path.segments(), ["typing", "Optional"]) { is_immutable_annotation(slice, semantic, extend_immutable_calls) - } else if is_pep_593_generic_type(call_path.as_slice()) { + } else if is_pep_593_generic_type(call_path.segments()) { if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() { elts.first().is_some_and(|elt| { is_immutable_annotation(elt, semantic, extend_immutable_calls) @@ -273,7 +273,7 @@ pub fn is_immutable_func( extend_immutable_calls: &[CallPath], ) -> bool { semantic.resolve_call_path(func).is_some_and(|call_path| { - is_immutable_return_type(call_path.as_slice()) + is_immutable_return_type(call_path.segments()) || extend_immutable_calls .iter() .any(|target| call_path == *target) @@ -285,7 +285,7 @@ pub fn is_mutable_func(func: &Expr, semantic: &SemanticModel) -> bool { semantic .resolve_call_path(func) .as_ref() - .map(CallPath::as_slice) + .map(CallPath::segments) .is_some_and(is_mutable_return_type) } @@ -337,7 +337,7 @@ pub fn is_sys_version_block(stmt: &ast::StmtIf, semantic: &SemanticModel) -> boo any_over_expr(test, &|expr| { semantic.resolve_call_path(expr).is_some_and(|call_path| { - matches!(call_path.as_slice(), ["sys", "version_info" | "platform"]) + matches!(call_path.segments(), ["sys", "version_info" | "platform"]) }) }) } @@ -620,7 +620,7 @@ impl TypeChecker for IoBaseChecker { return true; } matches!( - call_path.as_slice(), + call_path.segments(), [ "io", "IOBase" @@ -654,7 +654,7 @@ impl TypeChecker for IoBaseChecker { if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() { return semantic.resolve_call_path(func).is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "pathlib", "Path" | "PurePath" | "PurePosixPath" | "PureWindowsPath" @@ -670,7 +670,7 @@ impl TypeChecker for IoBaseChecker { .resolve_call_path(func.as_ref()) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["io", "open" | "open_code"] | ["os" | "", "open"] ) }) diff --git a/crates/ruff_python_semantic/src/analyze/visibility.rs b/crates/ruff_python_semantic/src/analyze/visibility.rs index 48836b9da6..2e129d265f 100644 --- a/crates/ruff_python_semantic/src/analyze/visibility.rs +++ b/crates/ruff_python_semantic/src/analyze/visibility.rs @@ -2,7 +2,7 @@ use std::path::Path; use ruff_python_ast::{self as ast, Decorator}; -use ruff_python_ast::call_path::{collect_call_path, CallPath}; +use ruff_python_ast::call_path::CallPath; use ruff_python_ast::helpers::map_callable; use crate::model::SemanticModel; @@ -18,7 +18,7 @@ pub fn is_staticmethod(decorator_list: &[Decorator], semantic: &SemanticModel) - decorator_list.iter().any(|decorator| { semantic .resolve_call_path(map_callable(&decorator.expression)) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "staticmethod"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["", "staticmethod"])) }) } @@ -27,7 +27,7 @@ pub fn is_classmethod(decorator_list: &[Decorator], semantic: &SemanticModel) -> decorator_list.iter().any(|decorator| { semantic .resolve_call_path(map_callable(&decorator.expression)) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "classmethod"])) + .is_some_and(|call_path| matches!(call_path.segments(), ["", "classmethod"])) }) } @@ -52,7 +52,7 @@ pub fn is_abstract(decorator_list: &[Decorator], semantic: &SemanticModel) -> bo .resolve_call_path(map_callable(&decorator.expression)) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), [ "abc", "abstractmethod" @@ -78,11 +78,11 @@ pub fn is_property( .resolve_call_path(map_callable(&decorator.expression)) .is_some_and(|call_path| { matches!( - call_path.as_slice(), + call_path.segments(), ["", "property"] | ["functools", "cached_property"] ) || extra_properties .iter() - .any(|extra_property| extra_property.as_slice() == call_path.as_slice()) + .any(|extra_property| extra_property.segments() == call_path.segments()) }) }) } @@ -187,9 +187,9 @@ pub(crate) fn function_visibility(function: &ast::StmtFunctionDef) -> Visibility pub fn method_visibility(function: &ast::StmtFunctionDef) -> Visibility { // Is this a setter or deleter? if function.decorator_list.iter().any(|decorator| { - collect_call_path(&decorator.expression).is_some_and(|call_path| { - call_path.as_slice() == [function.name.as_str(), "setter"] - || call_path.as_slice() == [function.name.as_str(), "deleter"] + CallPath::from_expr(&decorator.expression).is_some_and(|call_path| { + call_path.segments() == [function.name.as_str(), "setter"] + || call_path.segments() == [function.name.as_str(), "deleter"] }) }) { return Visibility::Private; diff --git a/crates/ruff_python_semantic/src/binding.rs b/crates/ruff_python_semantic/src/binding.rs index 9057c60de1..3ab5c76794 100644 --- a/crates/ruff_python_semantic/src/binding.rs +++ b/crates/ruff_python_semantic/src/binding.rs @@ -4,7 +4,7 @@ use std::ops::{Deref, DerefMut}; use bitflags::bitflags; use ruff_index::{newtype_index, IndexSlice, IndexVec}; -use ruff_python_ast::call_path::format_call_path; +use ruff_python_ast::call_path::format_call_path_segments; use ruff_python_ast::Stmt; use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; @@ -560,7 +560,9 @@ pub trait Imported<'a> { /// Returns the fully-qualified name of the imported symbol. fn qualified_name(&self) -> String { - format_call_path(self.call_path()) + let mut output = String::new(); + format_call_path_segments(self.call_path(), &mut output).unwrap(); + output } } @@ -601,7 +603,7 @@ impl<'a> Imported<'a> for SubmoduleImport<'a> { impl<'a> Imported<'a> for FromImport<'a> { /// For example, given `from foo import bar`, returns `["foo", "bar"]`. fn call_path(&self) -> &[&str] { - self.call_path.as_ref() + &self.call_path } /// For example, given `from foo import bar`, returns `["foo"]`. diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 51a2607cd8..4eed5b5ecd 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -2,9 +2,8 @@ use std::path::Path; use bitflags::bitflags; use rustc_hash::FxHashMap; -use smallvec::smallvec; -use ruff_python_ast::call_path::{collect_call_path, from_unqualified_name, CallPath}; +use ruff_python_ast::call_path::{CallPath, CallPathBuilder}; use ruff_python_ast::helpers::from_relative_import; use ruff_python_ast::{self as ast, Expr, Operator, Stmt}; use ruff_python_stdlib::path::is_python_stub_file; @@ -181,16 +180,18 @@ impl<'a> SemanticModel<'a> { /// Return `true` if the call path is a reference to `typing.${target}`. pub fn match_typing_call_path(&self, call_path: &CallPath, target: &str) -> bool { if matches!( - call_path.as_slice(), + call_path.segments(), ["typing" | "_typeshed" | "typing_extensions", member] if *member == target ) { return true; } if self.typing_modules.iter().any(|module| { - let mut module: CallPath = from_unqualified_name(module); - module.push(target); - *call_path == module + let module = CallPath::from_unqualified_name(module); + let mut builder = CallPathBuilder::from_path(module); + builder.push(target); + let target_path = builder.build(); + call_path == &target_path }) { return true; } @@ -567,10 +568,10 @@ impl<'a> SemanticModel<'a> { /// associated with `Class`, then the `BindingKind::FunctionDefinition` associated with /// `Class.method`. pub fn lookup_attribute(&'a self, value: &'a Expr) -> Option { - let call_path = collect_call_path(value)?; + let call_path = CallPath::from_expr(value)?; // Find the symbol in the current scope. - let (symbol, attribute) = call_path.split_first()?; + let (symbol, attribute) = call_path.segments().split_first()?; let mut binding_id = self.lookup_symbol(symbol)?; // Recursively resolve class attributes, e.g., `foo.bar.baz` in. @@ -677,25 +678,22 @@ impl<'a> SemanticModel<'a> { match &binding.kind { BindingKind::Import(Import { call_path }) => { - let value_path = collect_call_path(value)?; - let (_, tail) = value_path.split_first()?; + let value_path = CallPath::from_expr(value)?; + let (_, tail) = value_path.segments().split_first()?; let resolved: CallPath = call_path.iter().chain(tail.iter()).copied().collect(); Some(resolved) } BindingKind::SubmoduleImport(SubmoduleImport { call_path }) => { - let value_path = collect_call_path(value)?; - let (_, tail) = value_path.split_first()?; - let resolved: CallPath = call_path - .iter() - .take(1) - .chain(tail.iter()) - .copied() - .collect(); - Some(resolved) + let value_path = CallPath::from_expr(value)?; + let (_, tail) = value_path.segments().split_first()?; + let mut builder = CallPathBuilder::with_capacity(1 + tail.len()); + builder.extend(call_path.iter().copied().take(1)); + builder.extend(tail.iter().copied()); + Some(builder.build()) } BindingKind::FromImport(FromImport { call_path }) => { - let value_path = collect_call_path(value)?; - let (_, tail) = value_path.split_first()?; + let value_path = CallPath::from_expr(value)?; + let (_, tail) = value_path.segments().split_first()?; let resolved: CallPath = if call_path.first().map_or(false, |segment| *segment == ".") { @@ -708,24 +706,24 @@ impl<'a> SemanticModel<'a> { BindingKind::Builtin => { if value.is_name_expr() { // Ex) `dict` - Some(smallvec!["", head.id.as_str()]) + Some(CallPath::from_slice(&["", head.id.as_str()])) } else { // Ex) `dict.__dict__` - let value_path = collect_call_path(value)?; + let value_path = CallPath::from_expr(value)?; Some( std::iter::once("") - .chain(value_path.iter().copied()) + .chain(value_path.segments().iter().copied()) .collect(), ) } } BindingKind::ClassDefinition(_) | BindingKind::FunctionDefinition(_) => { - let value_path = collect_call_path(value)?; + let value_path = CallPath::from_expr(value)?; let resolved: CallPath = self .module_path? .iter() .map(String::as_str) - .chain(value_path) + .chain(value_path.segments().iter().copied()) .collect(); Some(resolved) }