use lazy_static::lazy_static;
use regex::Regex;
use std::{
cmp::min,
fmt::{Display, Formatter},
};
use itertools::{chain, izip, Itertools};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use crate::error::{error, Result};
pub fn ident(name: &str) -> Ident {
Ident::new(name, Span::call_site())
}
pub fn safe_ident(name: &str) -> Ident {
syn::parse_str::<Ident>(name).unwrap_or_else(|_| ident(&format!("{name}_")))
}
#[derive(Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct TypePath {
parts: Vec<Ident>,
is_absolute: bool,
}
impl Display for TypePath {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let prefix = if self.is_absolute { "::" } else { "" };
let parts_str = self.parts.iter().join("::");
write!(f, "{prefix}{parts_str}")
}
}
impl From<&Ident> for TypePath {
fn from(value: &Ident) -> Self {
TypePath::new(value).expect("All Idents are valid TypePaths")
}
}
impl From<Ident> for TypePath {
fn from(value: Ident) -> Self {
(&value).into()
}
}
impl TypePath {
pub fn new<T: ToString>(path: T) -> Result<Self> {
let path_str = path.to_string();
if path_str.trim().is_empty() {
return Ok(Self {
parts: vec![],
is_absolute: false,
});
}
let is_absolute = Self::is_absolute(&path_str);
let parts = path_str
.split("::")
.skip(is_absolute as usize)
.map(|part| {
let trimmed_part = part.trim().to_string();
if trimmed_part.is_empty() {
return Err(error!("TypePath cannot be constructed from '{path_str}' since it has it has empty parts"))
}
Ok(ident(&trimmed_part))
})
.collect::<Result<Vec<_>>>()?;
Ok(Self { parts, is_absolute })
}
fn len(&self) -> usize {
self.parts.len()
}
fn starts_with(&self, path: &TypePath) -> bool {
if self.parts.len() < path.parts.len() {
false
} else {
self.parts[..path.parts.len()] == path.parts
}
}
pub fn relative_path_from(&self, path: &TypePath) -> TypePath {
let our_parent = self.parent();
let number_of_consecutively_matching_parts = izip!(&our_parent.parts, &path.parts)
.enumerate()
.find_map(|(matches_so_far, (our_part, their_part))| {
(our_part != their_part).then_some(matches_so_far)
})
.unwrap_or_else(|| min(our_parent.len(), path.len()));
let prefix = if our_parent.starts_with(path) {
vec![ident("self")]
} else {
vec![ident("super"); path.len() - number_of_consecutively_matching_parts]
};
let non_matching_path_parts = our_parent
.parts
.iter()
.skip(number_of_consecutively_matching_parts)
.cloned();
let type_ident = self.ident().cloned();
TypePath {
parts: chain!(prefix, non_matching_path_parts, type_ident).collect(),
is_absolute: false,
}
}
pub fn parent(&self) -> TypePath {
let parts = if self.parts.is_empty() {
vec![]
} else {
self.parts[..self.parts.len() - 1].to_vec()
};
TypePath {
parts,
is_absolute: self.is_absolute,
}
}
pub fn take_parts(self) -> Vec<Ident> {
self.parts
}
pub fn has_multiple_parts(&self) -> bool {
self.parts.len() > 1
}
fn is_absolute(path_str: &str) -> bool {
path_str.trim_start().starts_with("::")
}
pub fn prepend(self, mut another: TypePath) -> Self {
another.parts.extend(self.parts);
another
}
pub fn append(mut self, another: TypePath) -> Self {
self.parts.extend(another.parts);
self
}
pub fn ident(&self) -> Option<&Ident> {
self.parts.last()
}
}
impl ToTokens for TypePath {
fn to_tokens(&self, tokens: &mut TokenStream) {
let parts = &self.parts;
let leading_delimiter = self.is_absolute.then_some(quote! {::});
tokens.extend(quote! { #leading_delimiter #(#parts)::* });
}
}
pub fn has_tuple_format(type_name: &str) -> bool {
type_name.starts_with('(') && type_name.ends_with(')')
}
pub fn extract_generic_name(type_name: &str) -> Option<String> {
lazy_static! {
static ref RE: Regex = Regex::new(r"^\s*generic\s+(\S+)\s*$").unwrap();
}
RE.captures(type_name)
.map(|captures| String::from(&captures[1]))
}
pub fn extract_array_len(type_name: &str) -> Option<usize> {
lazy_static! {
static ref RE: Regex = Regex::new(r"^\s*\[.+;\s*(\d+)\s*\]\s*$").unwrap();
}
RE.captures(type_name)
.map(|captures| captures[1].to_string())
.map(|length: String| {
length.parse::<usize>().unwrap_or_else(|_| {
panic!("Could not extract array length from {length}! Original field {type_name}")
})
})
}
pub fn extract_str_len(type_name: &str) -> Option<usize> {
lazy_static! {
static ref RE: Regex = Regex::new(r"^\s*str\s*\[\s*(\d+)\s*\]\s*$").unwrap();
}
RE.captures(type_name)
.map(|captures| captures[1].to_string())
.map(|length: String| {
length.parse::<usize>().unwrap_or_else(|_| {
panic!(
"Could not extract string length from {length}! Original field '{type_name}'"
)
})
})
}
pub fn extract_custom_type_name(type_field: &str) -> Option<String> {
lazy_static! {
static ref RE: Regex = Regex::new(r"\s*(?:struct|enum)\s*(\S*)").unwrap();
}
RE.captures(type_field)
.map(|captures| String::from(&captures[1]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_be_empty() {
let empty_path = " ";
let type_path = TypePath::new(empty_path).unwrap();
assert!(type_path.take_parts().is_empty());
}
#[test]
fn must_have_ident_at_end() {
let no_ident = " ::missing_ident:: ";
let err = TypePath::new(no_ident).expect_err("Should have failed!");
assert_eq!(
err.to_string(),
"TypePath cannot be constructed from ' ::missing_ident:: ' since it has it has empty parts"
);
}
#[test]
fn trims_whitespace() {
let path = " some_mod :: ident ";
let path = TypePath::new(path).expect("Should have passed.");
assert_eq!(path.parts, vec!["some_mod", "ident"])
}
#[test]
fn can_be_prepended_to() {
let path = TypePath::new(" some_mod :: ident ").expect("Should have passed.");
let another_path = TypePath::new(" something :: else ").expect("the type path is valid");
let joined = path.prepend(another_path);
assert_eq!(joined.parts, vec!["something", "else", "some_mod", "ident"])
}
#[test]
fn can_handle_absolute_paths() {
let absolute_path = " ::std :: vec:: Vec";
let type_path = TypePath::new(absolute_path);
type_path.unwrap();
}
#[test]
fn leading_delimiter_present_when_path_is_absolute() {
let type_path = TypePath::new(" ::std :: vec:: Vec").unwrap();
let tokens = type_path.to_token_stream();
let expected = quote! {::std::vec::Vec};
assert_eq!(expected.to_string(), tokens.to_string())
}
#[test]
fn leading_delimiter_not_present_when_path_is_relative() {
let type_path = TypePath::new(" std :: vec:: Vec").unwrap();
let tokens = type_path.to_token_stream();
let expected = quote! {std::vec::Vec};
assert_eq!(expected.to_string(), tokens.to_string())
}
#[test]
fn path_with_two_or_more_parts_has_a_parent() {
let type_path = TypePath::new(":: std::Type").unwrap();
let parent = type_path.parent();
let expected_parent = TypePath::new("::std").unwrap();
assert_eq!(parent, expected_parent)
}
#[test]
fn path_with_only_one_part_has_empty_parent() {
let type_path = TypePath::new(":: std").unwrap();
let parent = type_path.parent();
assert!(parent.take_parts().is_empty());
}
#[test]
fn relative_path_from_same_mod() {
let deeper_path = TypePath::new("a::b::SomeType").unwrap();
let the_same_mod = TypePath::new("a::b").unwrap();
let relative_path = deeper_path.relative_path_from(&the_same_mod);
let expected_relative_path = TypePath::new("self::SomeType").unwrap();
assert_eq!(relative_path, expected_relative_path);
}
#[test]
fn relative_path_from_root_mod() {
let deeper_path = TypePath::new("SomeType").unwrap();
let root_mod = TypePath::new("").unwrap();
let relative_path = deeper_path.relative_path_from(&root_mod);
let expected_relative_path = TypePath::new("self::SomeType").unwrap();
assert_eq!(relative_path, expected_relative_path);
}
#[test]
fn relative_path_from_deeper_mod() {
let a_path = TypePath::new("a::b::SomeType").unwrap();
let deeper_mod = TypePath::new("a::b::c::d").unwrap();
let relative_path = a_path.relative_path_from(&deeper_mod);
let expected_relative_path = TypePath::new("super::super::SomeType").unwrap();
assert_eq!(relative_path, expected_relative_path);
}
#[test]
fn relative_path_going_deeper() {
let a_path = TypePath::new("a::b::c::SomeType").unwrap();
let higher_level_mod = TypePath::new("a").unwrap();
let relative_path = a_path.relative_path_from(&higher_level_mod);
let expected_relative_path = TypePath::new("self::b::c::SomeType").unwrap();
assert_eq!(relative_path, expected_relative_path);
}
#[test]
fn relative_path_up_then_down() {
let a_path = TypePath::new("a::b::c::SomeType").unwrap();
let sister_path = TypePath::new("d::e").unwrap();
let relative_path = a_path.relative_path_from(&sister_path);
let expected_relative_path = TypePath::new("super::super::a::b::c::SomeType").unwrap();
assert_eq!(relative_path, expected_relative_path);
}
#[test]
fn path_starts_with_another() {
let a_path = TypePath::new("a::b::c::d").unwrap();
let prefix = TypePath::new("a::b").unwrap();
assert!(a_path.starts_with(&prefix));
}
#[test]
fn path_does_not_start_with_another() {
let a_path = TypePath::new("a::b::c::d").unwrap();
let prefix = TypePath::new("c::d").unwrap();
assert!(!a_path.starts_with(&prefix));
}
#[test]
fn start_with_size_guard() {
let a_path = TypePath::new("a::b::c").unwrap();
let prefix = TypePath::new("a::b::c::d").unwrap();
assert!(!a_path.starts_with(&prefix));
}
#[test]
fn tuples_start_and_end_with_round_brackets() {
assert!(has_tuple_format("(_, _)"));
assert!(!has_tuple_format("(.."));
assert!(!has_tuple_format("..)"));
}
#[test]
fn generic_name_extracted() {
let type_name = " generic T ";
let name = extract_generic_name(type_name).expect("Should have succeeded");
assert_eq!(name, "T");
}
#[test]
fn array_len_extracted() {
let type_name = " [ _ ; 8 ] ";
let size = extract_array_len(type_name).expect("Should have succeeded");
assert_eq!(size, 8);
}
#[test]
fn str_len_extracted() {
let type_name = " str [ 10 ] ";
let str_len = extract_str_len(type_name).expect("Should have succeeded");
assert_eq!(str_len, 10);
}
#[test]
fn custom_struct_type_name_extracted() {
let type_name = " struct SomeStruct ";
let struct_name = extract_custom_type_name(type_name).expect("Should have succeeded");
assert_eq!(struct_name, "SomeStruct");
}
#[test]
fn custom_enum_type_name_extracted() {
let type_name = " enum SomeEnum ";
let enum_name = extract_custom_type_name(type_name).expect("Should have succeeded");
assert_eq!(enum_name, "SomeEnum");
}
}