use std::collections::HashMap;
use syn::{Ident, Path, PathArguments, PathSegment};
#[cfg(feature = "static-resolver")]
use phf::Map;
pub trait MappingStorage {
fn get(&self, path: &str) -> Option<&str>;
fn contains_key(&self, path: &str) -> bool;
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn keys(&self) -> Box<dyn Iterator<Item = &str> + '_>;
fn values(&self) -> Box<dyn Iterator<Item = &str> + '_>;
}
impl MappingStorage for HashMap<String, String> {
fn get(&self, path: &str) -> Option<&str> {
self.get(path).map(|s| s.as_str())
}
fn contains_key(&self, path: &str) -> bool {
HashMap::contains_key(self, path)
}
fn len(&self) -> usize {
HashMap::len(self)
}
fn is_empty(&self) -> bool {
HashMap::is_empty(self)
}
fn keys(&self) -> Box<dyn Iterator<Item = &str> + '_> {
Box::new(HashMap::keys(self).map(|s| s.as_str()))
}
fn values(&self) -> Box<dyn Iterator<Item = &str> + '_> {
Box::new(HashMap::values(self).map(|s| s.as_str()))
}
}
#[cfg(feature = "static-resolver")]
impl MappingStorage for Map<&'static str, &'static str> {
fn get(&self, path: &str) -> Option<&str> {
Map::get(self, path).copied()
}
fn contains_key(&self, path: &str) -> bool {
Map::contains_key(self, path)
}
fn len(&self) -> usize {
Map::len(self)
}
fn is_empty(&self) -> bool {
Map::is_empty(self)
}
fn keys(&self) -> Box<dyn Iterator<Item = &str> + '_> {
Box::new(Map::keys(self).copied())
}
fn values(&self) -> Box<dyn Iterator<Item = &str> + '_> {
Box::new(Map::values(self).copied())
}
}
#[cfg(feature = "static-resolver")]
impl MappingStorage for &Map<&'static str, &'static str> {
fn get(&self, path: &str) -> Option<&str> {
Map::get(*self, path).copied()
}
fn contains_key(&self, path: &str) -> bool {
Map::contains_key(*self, path)
}
fn len(&self) -> usize {
Map::len(*self)
}
fn is_empty(&self) -> bool {
Map::is_empty(*self)
}
fn keys(&self) -> Box<dyn Iterator<Item = &str> + '_> {
Box::new(Map::keys(*self).copied())
}
fn values(&self) -> Box<dyn Iterator<Item = &str> + '_> {
Box::new(Map::values(*self).copied())
}
}
#[derive(Debug, Clone, Copy)]
pub struct EmptyStorage;
impl MappingStorage for EmptyStorage {
fn get(&self, _path: &str) -> Option<&str> {
None
}
fn contains_key(&self, _path: &str) -> bool {
false
}
fn len(&self) -> usize {
0
}
fn is_empty(&self) -> bool {
true
}
fn keys(&self) -> Box<dyn Iterator<Item = &str> + '_> {
Box::new(std::iter::empty())
}
fn values(&self) -> Box<dyn Iterator<Item = &str> + '_> {
Box::new(std::iter::empty())
}
}
pub trait StripRaw {
type Output;
fn strip_raw(&self) -> Self::Output;
}
pub trait HasRaw {
fn has_raw(&self) -> bool;
}
impl StripRaw for Ident {
type Output = Ident;
fn strip_raw(&self) -> Self::Output {
let ident_str = self.to_string();
if let Some(stripped) = ident_str.strip_prefix("r#") {
Ident::new(stripped, self.span())
} else {
self.clone()
}
}
}
impl HasRaw for Ident {
fn has_raw(&self) -> bool {
self.to_string().starts_with("r#")
}
}
impl StripRaw for PathSegment {
type Output = PathSegment;
fn strip_raw(&self) -> Self::Output {
PathSegment {
ident: self.ident.strip_raw(),
arguments: self.arguments.clone(),
}
}
}
impl HasRaw for PathSegment {
fn has_raw(&self) -> bool {
self.ident.has_raw()
}
}
impl StripRaw for Path {
type Output = Path;
fn strip_raw(&self) -> Self::Output {
Path {
leading_colon: self.leading_colon,
segments: self.segments.iter().map(|seg| seg.strip_raw()).collect(),
}
}
}
impl HasRaw for Path {
fn has_raw(&self) -> bool {
self.segments.iter().any(|seg| seg.has_raw())
}
}
pub mod utils {
use syn::Ident;
pub fn is_raw_ident(s: &str) -> bool {
s.starts_with("r#")
}
pub fn strip_raw_prefix(s: &str) -> &str {
if is_raw_ident(s) { &s[2..] } else { s }
}
pub fn ident_from_string(s: &str) -> syn::Result<Ident> {
if is_raw_ident(s) {
syn::parse_str(&format!("r#{}", &s[2..]))
} else {
syn::parse_str(s)
}
}
}
#[cfg(feature = "static-resolver")]
fn get_primitive_mapping_static(path: &str) -> Option<&'static str> {
match path {
"std::primitive::i8" | "core::primitive::i8" | "std::i8" | "core::i8" => Some("i8"),
"std::primitive::i16" | "core::primitive::i16" | "std::i16" | "core::i16" => Some("i16"),
"std::primitive::i32" | "core::primitive::i32" | "std::i32" | "core::i32" => Some("i32"),
"std::primitive::i64" | "core::primitive::i64" | "std::i64" | "core::i64" => Some("i64"),
"std::primitive::i128" | "core::primitive::i128" | "std::i128" | "core::i128" => {
Some("i128")
}
"std::primitive::isize" | "core::primitive::isize" | "std::isize" | "core::isize" => {
Some("isize")
}
"std::primitive::u8" | "core::primitive::u8" | "std::u8" | "core::u8" => Some("u8"),
"std::primitive::u16" | "core::primitive::u16" | "std::u16" | "core::u16" => Some("u16"),
"std::primitive::u32" | "core::primitive::u32" | "std::u32" | "core::u32" => Some("u32"),
"std::primitive::u64" | "core::primitive::u64" | "std::u64" | "core::u64" => Some("u64"),
"std::primitive::u128" | "core::primitive::u128" | "std::u128" | "core::u128" => {
Some("u128")
}
"std::primitive::usize" | "core::primitive::usize" | "std::usize" | "core::usize" => {
Some("usize")
}
"std::primitive::f32" | "core::primitive::f32" | "std::f32" | "core::f32" => Some("f32"),
"std::primitive::f64" | "core::primitive::f64" | "std::f64" | "core::f64" => Some("f64"),
"std::primitive::bool" | "core::primitive::bool" | "std::bool" | "core::bool" => {
Some("bool")
}
"std::primitive::char" | "core::primitive::char" | "std::char" | "core::char" => {
Some("char")
}
"std::primitive::str" | "core::primitive::str" | "std::str" | "core::str" => Some("str"),
"std::string::String" => Some("String"),
"std::vec::Vec" => Some("Vec"),
"std::collections::HashMap" => Some("HashMap"),
"std::collections::HashSet" => Some("HashSet"),
"std::option::Option" => Some("Option"),
"std::result::Result" => Some("Result"),
_ => None,
}
}
#[cfg(not(feature = "static-resolver"))]
fn get_primitive_mapping(path: &str) -> Option<&'static str> {
match path {
"std::primitive::i8" | "core::primitive::i8" | "std::i8" | "core::i8" => Some("i8"),
"std::primitive::i16" | "core::primitive::i16" | "std::i16" | "core::i16" => Some("i16"),
"std::primitive::i32" | "core::primitive::i32" | "std::i32" | "core::i32" => Some("i32"),
"std::primitive::i64" | "core::primitive::i64" | "std::i64" | "core::i64" => Some("i64"),
"std::primitive::i128" | "core::primitive::i128" | "std::i128" | "core::i128" => {
Some("i128")
}
"std::primitive::isize" | "core::primitive::isize" | "std::isize" | "core::isize" => {
Some("isize")
}
"std::primitive::u8" | "core::primitive::u8" | "std::u8" | "core::u8" => Some("u8"),
"std::primitive::u16" | "core::primitive::u16" | "std::u16" | "core::u16" => Some("u16"),
"std::primitive::u32" | "core::primitive::u32" | "std::u32" | "core::u32" => Some("u32"),
"std::primitive::u64" | "core::primitive::u64" | "std::u64" | "core::u64" => Some("u64"),
"std::primitive::u128" | "core::primitive::u128" | "std::u128" | "core::u128" => {
Some("u128")
}
"std::primitive::usize" | "core::primitive::usize" | "std::usize" | "core::usize" => {
Some("usize")
}
"std::primitive::f32" | "core::primitive::f32" | "std::f32" | "core::f32" => Some("f32"),
"std::primitive::f64" | "core::primitive::f64" | "std::f64" | "core::f64" => Some("f64"),
"std::primitive::bool" | "core::primitive::bool" | "std::bool" | "core::bool" => {
Some("bool")
}
"std::primitive::char" | "core::primitive::char" | "std::char" | "core::char" => {
Some("char")
}
"std::primitive::str" | "core::primitive::str" | "std::str" | "core::str" => Some("str"),
"std::string::String" => Some("String"),
"std::vec::Vec" => Some("Vec"),
"std::collections::HashMap" => Some("HashMap"),
"std::collections::HashSet" => Some("HashSet"),
"std::option::Option" => Some("Option"),
"std::result::Result" => Some("Result"),
_ => None,
}
}
const PRIMITIVE_COUNT: usize = 74;
pub type DynamicPathResolver = PathResolver<HashMap<String, String>>;
#[cfg(feature = "static-resolver")]
pub type StaticPathResolver<'a> = PathResolver<&'a Map<&'static str, &'static str>>;
pub type PrimitivePathResolver = PathResolver<EmptyStorage>;
pub const EMPTY_RESOLVER: PrimitivePathResolver = PathResolver::new(EmptyStorage, false);
pub const PRIMITIVE_RESOLVER: PrimitivePathResolver = PathResolver::new(EmptyStorage, true);
#[derive(Debug, Clone)]
pub struct PathResolver<M> {
mappings: M,
use_primitives: bool,
}
impl<M> PathResolver<M>
where
M: MappingStorage,
{
pub const fn new(mappings: M, use_primitives: bool) -> Self {
Self {
mappings,
use_primitives,
}
}
pub const fn uses_primitives(&self) -> bool {
self.use_primitives
}
pub fn resolve(&self, path: &Path) -> Option<&str> {
let stripped = path.strip_raw();
let full_normalized = self.normalize_path(path);
if let Some(result) = self.try_resolve_base_type(&full_normalized) {
return Some(result);
}
if let Some(last_segment) = stripped.segments.last() {
let has_generics = !matches!(last_segment.arguments, PathArguments::None);
if has_generics {
let base_type = last_segment.ident.to_string();
if let Some(result) = self.resolve_with_progressive_paths(&stripped, &base_type) {
return Some(result);
}
}
}
if let Some(result) = self
.resolve_with_progressive_paths(&stripped, &stripped.segments.last()?.ident.to_string())
{
return Some(result);
}
None
}
fn resolve_with_progressive_paths(&self, path: &Path, base_type: &str) -> Option<&str> {
let segments: Vec<String> = path
.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect();
if segments.len() == 1 {
return self.find_mapping_ending_with(base_type);
}
for start_idx in 0..segments.len() {
let candidate_segments: Vec<String> = segments[start_idx..]
.iter()
.take(segments.len() - start_idx - 1) .cloned()
.collect();
if candidate_segments.is_empty() {
if let Some(result) = self.try_resolve_base_type(base_type) {
return Some(result);
}
if self.could_be_stdlib_shortening(&segments, base_type) {
return self.find_mapping_ending_with(base_type);
}
} else {
let mut full_candidate = candidate_segments;
full_candidate.push(base_type.to_string());
let candidate_path = full_candidate.join("::");
if let Some(result) = self.try_resolve_base_type(&candidate_path) {
return Some(result);
}
}
}
None
}
fn could_be_stdlib_shortening(&self, segments: &[String], base_type: &str) -> bool {
let common_types = ["Option", "Vec", "HashMap", "HashSet", "Result", "String"];
if !common_types.contains(&base_type) {
return false;
}
let stdlib_modules = [
"std",
"core",
"alloc",
"option",
"vec",
"collections",
"string",
"result",
];
for segment in segments {
if stdlib_modules.contains(&segment.as_str()) {
return true;
}
}
false
}
fn find_mapping_ending_with(&self, base_type: &str) -> Option<&str> {
if let Some(result) = self.try_resolve_base_type(base_type) {
return Some(result);
}
let suffix = format!("::{}", base_type);
let mut candidates: Vec<(&str, &str)> = Vec::new();
for key in self.mappings.keys() {
if key.ends_with(&suffix)
&& let Some(result) = self.try_resolve_base_type(key)
{
candidates.push((key, result));
}
}
if candidates.is_empty() {
if self.use_primitives {
#[cfg(feature = "static-resolver")]
{
if get_primitive_mapping_static(base_type).is_some() {
return get_primitive_mapping_static(base_type);
}
return self.check_primitive_patterns(base_type, get_primitive_mapping_static);
}
#[cfg(not(feature = "static-resolver"))]
{
if get_primitive_mapping(base_type).is_some() {
return get_primitive_mapping(base_type);
}
return self.check_primitive_patterns(base_type, get_primitive_mapping);
}
}
return None;
}
let mut stdlib_candidates: Vec<(&str, &str)> = Vec::new();
let mut other_candidates: Vec<(&str, &str)> = Vec::new();
for (key, result) in candidates {
if key.starts_with("std::") || key.starts_with("core::") || key.starts_with("alloc::") {
stdlib_candidates.push((key, result));
} else {
other_candidates.push((key, result));
}
}
let preferred_candidates = if !stdlib_candidates.is_empty() {
stdlib_candidates
} else {
other_candidates
};
let mut sorted_candidates = preferred_candidates;
sorted_candidates.sort_by(|a, b| {
let len_cmp = a.0.matches("::").count().cmp(&b.0.matches("::").count());
if len_cmp == std::cmp::Ordering::Equal {
a.0.cmp(b.0)
} else {
len_cmp
}
});
sorted_candidates.first().map(|(_, result)| *result)
}
fn check_primitive_patterns<F>(&self, base_type: &str, primitive_fn: F) -> Option<&str>
where
F: Fn(&str) -> Option<&'static str>,
{
for prefix in &["std", "core"] {
let candidate = format!("{}::{}", prefix, base_type);
if let Some(result) = primitive_fn(&candidate) {
return Some(result);
}
let candidate = format!("{}::primitive::{}", prefix, base_type);
if let Some(result) = primitive_fn(&candidate) {
return Some(result);
}
let candidate = format!("{}::string::{}", prefix, base_type);
if let Some(result) = primitive_fn(&candidate) {
return Some(result);
}
let candidate = format!("{}::vec::{}", prefix, base_type);
if let Some(result) = primitive_fn(&candidate) {
return Some(result);
}
let candidate = format!("{}::collections::{}", prefix, base_type);
if let Some(result) = primitive_fn(&candidate) {
return Some(result);
}
let candidate = format!("{}::option::{}", prefix, base_type);
if let Some(result) = primitive_fn(&candidate) {
return Some(result);
}
let candidate = format!("{}::result::{}", prefix, base_type);
if let Some(result) = primitive_fn(&candidate) {
return Some(result);
}
}
None
}
fn try_resolve_base_type(&self, base_type: &str) -> Option<&str> {
if let Some(canonical) = self.mappings.get(base_type) {
return Some(canonical);
}
if self.use_primitives {
#[cfg(feature = "static-resolver")]
let primitive_result = get_primitive_mapping_static(base_type);
#[cfg(not(feature = "static-resolver"))]
let primitive_result = get_primitive_mapping(base_type);
return primitive_result;
}
None
}
pub fn canonical_types(&self) -> impl Iterator<Item = &str> {
let custom_types: Vec<&str> = self.mappings.values().collect();
if self.use_primitives {
#[cfg(feature = "static-resolver")]
{
let primitive_types = vec![
"i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128",
"usize", "f32", "f64", "bool", "char", "str", "String", "Vec", "HashMap",
"HashSet", "Option", "Result",
];
let all_types: Vec<&str> =
custom_types.into_iter().chain(primitive_types).collect();
Box::new(all_types.into_iter()) as Box<dyn Iterator<Item = &str>>
}
#[cfg(not(feature = "static-resolver"))]
{
let primitive_types = vec![
"i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128",
"usize", "f32", "f64", "bool", "char", "str", "String", "Vec", "HashMap",
"HashSet", "Option", "Result",
];
let all_types: Vec<&str> =
custom_types.into_iter().chain(primitive_types).collect();
Box::new(all_types.into_iter()) as Box<dyn Iterator<Item = &str>>
}
} else {
Box::new(custom_types.into_iter()) as Box<dyn Iterator<Item = &str>>
}
}
pub fn path_patterns(&self) -> impl Iterator<Item = &str> {
let custom_patterns: Vec<&str> = self.mappings.keys().collect();
if self.use_primitives {
#[cfg(feature = "static-resolver")]
{
Box::new(custom_patterns.into_iter()) as Box<dyn Iterator<Item = &str>>
}
#[cfg(not(feature = "static-resolver"))]
{
Box::new(custom_patterns.into_iter()) as Box<dyn Iterator<Item = &str>>
}
} else {
Box::new(custom_patterns.into_iter()) as Box<dyn Iterator<Item = &str>>
}
}
pub fn has_mapping(&self, path: &Path) -> bool {
let normalized = self.normalize_path(path);
self.mappings.contains_key(&normalized)
|| (self.use_primitives && {
#[cfg(feature = "static-resolver")]
{
get_primitive_mapping_static(&normalized).is_some()
}
#[cfg(not(feature = "static-resolver"))]
{
get_primitive_mapping(&normalized).is_some()
}
})
}
pub fn len(&self) -> usize {
let custom_len = self.mappings.len();
if self.use_primitives {
custom_len + {
#[cfg(feature = "static-resolver")]
{
PRIMITIVE_COUNT
}
#[cfg(not(feature = "static-resolver"))]
{
PRIMITIVE_COUNT
}
}
} else {
custom_len
}
}
pub fn is_empty(&self) -> bool {
let custom_empty = self.mappings.is_empty();
custom_empty
&& (!self.use_primitives || {
#[cfg(feature = "static-resolver")]
{
PRIMITIVE_COUNT == 0
}
#[cfg(not(feature = "static-resolver"))]
{
PRIMITIVE_COUNT == 0
}
})
}
fn normalize_path(&self, path: &Path) -> String {
let stripped = path.strip_raw();
let segments: Vec<String> = stripped
.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect();
segments.join("::")
}
fn normalize_path_string(&self, path_str: &str) -> String {
let path_str = path_str.strip_prefix("::").unwrap_or(path_str);
let segments: Vec<&str> = path_str
.split("::")
.map(|segment| {
if let Some(stripped) = segment.strip_prefix("r#") {
stripped
} else {
segment
}
})
.collect();
segments.join("::")
}
}
impl DynamicPathResolver {
pub fn with_primitives() -> Self {
Self::new(HashMap::new(), true)
}
pub fn from_map(mappings: HashMap<String, String>, use_primitives: bool) -> Self {
Self::new(mappings, use_primitives)
}
pub fn set_use_primitives(&mut self, use_primitives: bool) {
self.use_primitives = use_primitives;
}
pub fn add_mapping<S1, S2>(&mut self, path_pattern: S1, canonical_type: S2)
where
S1: Into<String>,
S2: Into<String>,
{
let normalized_pattern = self.normalize_path_string(&path_pattern.into());
self.mappings
.insert(normalized_pattern, canonical_type.into());
}
pub fn clear(&mut self) {
self.mappings.clear();
}
}
impl Default for DynamicPathResolver {
fn default() -> Self {
Self::new(HashMap::new(), false)
}
}
impl PathResolver<EmptyStorage> {
pub const fn primitives_only() -> Self {
Self::new(EmptyStorage, true)
}
pub const fn empty() -> Self {
Self::new(EmptyStorage, false)
}
}
#[cfg(feature = "static-resolver")]
pub const fn create_static_resolver(
custom_mappings: &'static Map<&'static str, &'static str>,
use_primitives: bool,
) -> PathResolver<&'static Map<&'static str, &'static str>> {
PathResolver::new(custom_mappings, use_primitives)
}