Make CallPath its own struct

This commit is contained in:
Charlie Marsh 2023-04-01 12:03:50 -04:00
parent 5f5e71e81d
commit 2446cd49fa
7 changed files with 128 additions and 71 deletions

View File

@ -1,13 +1,112 @@
use rustpython_parser::ast::{Expr, ExprKind};
use smallvec::smallvec;
use std::fmt::Display;
/// A representation of a qualified name, like `typing.List`.
pub type CallPath<'a> = smallvec::SmallVec<[&'a str; 8]>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallPath<'a>(smallvec::SmallVec<[&'a str; 8]>);
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut CallPath<'a>) -> bool {
impl<'a> CallPath<'a> {
/// Create a new, empty [`CallPath`].
pub fn new() -> Self {
Self(smallvec![])
}
/// Create a new, empty [`CallPath`] with the given capacity.
pub fn with_capacity(capacity: usize) -> Self {
Self(smallvec::SmallVec::with_capacity(capacity))
}
/// Create a [`CallPath`] from an expression.
pub fn try_from_expr(expr: &'a Expr) -> Option<Self> {
let mut segments = CallPath::new();
collect_call_path(expr, &mut segments).then_some(segments)
}
/// Create a [`CallPath`] from a fully-qualified name.
///
/// ```rust
/// # use smallvec::smallvec;
/// # use ruff_python_ast::call_path::{CallPath, from_qualified_name};
///
/// assert_eq!(CallPath::from_qualified_name("typing.List").as_slice(), ["typing", "List"]);
/// assert_eq!(CallPath::from_qualified_name("list").as_slice(), ["", "list"]);
/// ```
pub fn from_qualified_name(name: &'a str) -> Self {
Self(if name.contains('.') {
name.split('.').collect()
} else {
// Special-case: for builtins, return `["", "int"]` instead of `["int"]`.
smallvec!["", name]
})
}
/// Create a [`CallPath`] from an unqualified name.
///
/// ```rust
/// # use smallvec::smallvec;
/// # use ruff_python_ast::call_path::{CallPath, from_unqualified_name};
///
/// assert_eq!(CallPath::from_unqualified_name("typing.List").as_slice(), ["typing", "List"]);
/// assert_eq!(CallPath::from_unqualified_name("list").as_slice(), ["list"]);
/// ```
pub fn from_unqualified_name(name: &'a str) -> Self {
Self(name.split('.').collect())
}
pub fn push(&mut self, segment: &'a str) {
self.0.push(segment)
}
pub fn pop(&mut self) -> Option<&'a str> {
self.0.pop()
}
pub fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
self.0.extend(iter)
}
pub fn first(&self) -> Option<&&'a str> {
self.0.first()
}
pub fn last(&self) -> Option<&&'a str> {
self.0.last()
}
pub fn as_slice(&self) -> &[&str] {
self.0.as_slice()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn starts_with(&self, other: &Self) -> bool {
self.0.starts_with(&other.0)
}
}
impl<'a> IntoIterator for CallPath<'a> {
type Item = &'a str;
type IntoIter = smallvec::IntoIter<[&'a str; 8]>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Display for CallPath<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", format_call_path(self.as_slice()))
}
}
/// Collect a [`CallPath`] from an [`Expr`].
fn collect_call_path<'a>(expr: &'a Expr, parts: &mut CallPath<'a>) -> bool {
match &expr.node {
ExprKind::Attribute { value, attr, .. } => {
if collect_call_path_inner(value, parts) {
if collect_call_path(value, parts) {
parts.push(attr);
true
} else {
@ -22,19 +121,8 @@ fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut CallPath<'a>) -> bool
}
}
/// Convert an `Expr` to its [`CallPath`] segments (like `["typing", "List"]`).
pub fn collect_call_path(expr: &Expr) -> Option<CallPath> {
let mut segments = smallvec![];
collect_call_path_inner(expr, &mut segments).then_some(segments)
}
/// Convert an `Expr` to its call path (like `List`, or `typing.List`).
pub fn compose_call_path(expr: &Expr) -> Option<String> {
collect_call_path(expr).map(|call_path| format_call_path(&call_path))
}
/// Format a call path for display.
pub fn format_call_path(call_path: &[&str]) -> String {
/// Format a [`CallPath`] for display.
fn format_call_path(call_path: &[&str]) -> String {
if call_path
.first()
.expect("Unable to format empty call path")
@ -45,34 +133,3 @@ pub fn format_call_path(call_path: &[&str]) -> String {
call_path.join(".")
}
}
/// 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]
}
}

View File

@ -3,7 +3,6 @@ use std::path::Path;
use nohash_hasher::{BuildNoHashHasher, IntMap};
use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Expr, Stmt};
use smallvec::smallvec;
use ruff_python_stdlib::path::is_python_stub_file;
use ruff_python_stdlib::typing::TYPING_EXTENSIONS;
@ -12,7 +11,7 @@ use crate::binding::{
Binding, BindingId, BindingKind, Bindings, Exceptions, ExecutionContext, FromImportation,
Importation, SubmoduleImportation,
};
use crate::call_path::{collect_call_path, from_unqualified_name, CallPath};
use crate::call_path::CallPath;
use crate::helpers::from_relative_import;
use crate::scope::{Scope, ScopeId, ScopeKind, ScopeStack, Scopes};
use crate::types::RefEquality;
@ -105,7 +104,7 @@ impl<'a> Context<'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 {
pub fn match_typing_call_path(&self, call_path: &CallPath<'a>, target: &'a str) -> bool {
if call_path.as_slice() == ["typing", target] {
return true;
}
@ -117,7 +116,7 @@ impl<'a> Context<'a> {
}
if self.typing_modules.iter().any(|module| {
let mut module: CallPath = from_unqualified_name(module);
let mut module = CallPath::from_unqualified_name(module);
module.push(target);
*call_path == module
}) {
@ -156,7 +155,7 @@ impl<'a> Context<'a> {
where
'b: 'a,
{
let Some(call_path) = collect_call_path(value) else {
let Some(call_path) = CallPath::try_from_expr(value) else {
return None;
};
let Some(head) = call_path.first() else {
@ -179,7 +178,7 @@ impl<'a> Context<'a> {
None
}
} else {
let mut source_path: CallPath = from_unqualified_name(name);
let mut source_path = CallPath::from_unqualified_name(name);
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
}
@ -196,13 +195,13 @@ impl<'a> Context<'a> {
None
}
} else {
let mut source_path: CallPath = from_unqualified_name(name);
let mut source_path = CallPath::from_unqualified_name(name);
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
}
}
BindingKind::Builtin => {
let mut source_path: CallPath = smallvec![];
let mut source_path = CallPath::with_capacity(call_path.len() + 1);
source_path.push("");
source_path.extend(call_path);
Some(source_path)

View File

@ -1,6 +1,6 @@
use rustpython_parser::ast::Expr;
use crate::call_path::from_qualified_name;
use crate::call_path::CallPath;
use crate::context::Context;
use crate::helpers::map_callable;
use crate::scope::{Scope, ScopeKind};
@ -36,7 +36,7 @@ pub fn classify(
call_path.as_slice() == ["", "staticmethod"]
|| staticmethod_decorators
.iter()
.any(|decorator| call_path == from_qualified_name(decorator))
.any(|decorator| call_path == CallPath::from_qualified_name(decorator))
})
}) {
FunctionType::StaticMethod
@ -56,7 +56,7 @@ pub fn classify(
call_path.as_slice() == ["", "classmethod"] ||
classmethod_decorators
.iter()
.any(|decorator| call_path == from_qualified_name(decorator))
.any(|decorator| call_path == CallPath::from_qualified_name(decorator))
})
})
{

View File

@ -717,7 +717,7 @@ pub fn to_module_path(package: &Path, path: &Path) -> Option<Vec<String>> {
/// Create a call path from a relative import.
pub fn from_relative_import<'a>(module: &'a [String], name: &'a str) -> CallPath<'a> {
let mut call_path: CallPath = SmallVec::with_capacity(module.len() + 1);
let mut call_path: CallPath = CallPath::with_capacity(module.len() + 1);
// Start with the module path.
call_path.extend(module.iter().map(String::as_str));

View File

@ -1,6 +1,6 @@
use crate::call_path::CallPath;
use rustpython_parser::ast::{Expr, ExprKind};
use crate::call_path::collect_call_path;
use crate::context::Context;
#[derive(Copy, Clone)]
@ -43,16 +43,18 @@ impl LoggingLevel {
/// ```
pub fn is_logger_candidate(context: &Context, func: &Expr) -> bool {
if let ExprKind::Attribute { value, .. } = &func.node {
let Some(call_path) = context
if let Some(call_path) = context
.resolve_call_path(value)
.or_else(|| collect_call_path(value)) else {
return false;
};
.or_else(|| CallPath::try_from_expr(value))
{
let tail = call_path.last();
if let Some(tail) = call_path.last() {
if tail.starts_with("log") || tail.ends_with("logger") || tail.ends_with("logging") {
if tail.starts_with("log") || tail.ends_with("logger") || tail.ends_with("logging")
{
return true;
}
}
}
}
false
}

View File

@ -4,7 +4,7 @@ use rustpython_parser::ast::{Expr, ExprKind, Location};
use ruff_python_stdlib::typing::{PEP_585_BUILTINS_ELIGIBLE, PEP_593_SUBSCRIPTS, SUBSCRIPTS};
use crate::call_path::{from_unqualified_name, CallPath};
use crate::call_path::CallPath;
use crate::context::Context;
use crate::relocate::relocate_expr;
use crate::source_code::Locator;
@ -48,7 +48,7 @@ pub fn match_annotated_subscript<'a>(
}
for module in typing_modules {
let module_call_path: CallPath = from_unqualified_name(module);
let module_call_path = CallPath::from_qualified_name(module);
if call_path.starts_with(&module_call_path) {
for subscript in SUBSCRIPTS.iter() {
if call_path.last() == subscript.last() {

View File

@ -2,7 +2,6 @@ use std::path::Path;
use rustpython_parser::ast::{Expr, Stmt, StmtKind};
use crate::call_path::collect_call_path;
use crate::call_path::CallPath;
use crate::context::Context;
use crate::helpers::map_callable;
@ -183,7 +182,7 @@ pub fn method_visibility(stmt: &Stmt) -> Visibility {
} => {
// Is this a setter or deleter?
if decorator_list.iter().any(|expr| {
collect_call_path(expr).map_or(false, |call_path| {
CallPath::try_from_expr(expr).map_or(false, |call_path| {
call_path.as_slice() == [name, "setter"]
|| call_path.as_slice() == [name, "deleter"]
})