use std::collections::HashMap;
use proc_macro2::TokenStream as TokenStream2;
use crate::ImportInfo;
pub(crate) type TypeMap = HashMap<String, String>;
fn build_import_map(imports: &[ImportInfo]) -> HashMap<String, String> {
imports.iter().map(|i| (i.name.clone(), i.path.clone())).collect()
}
fn resolve_type(ty: &TokenStream2, import_map: &HashMap<String, String>) -> String {
let ty_str = ty.to_string();
if ty_str == "()" {
return "()".to_string();
}
if let Ok(parsed) = syn::parse2::<syn::Type>(ty.clone()) {
return resolve_syn_type(&parsed, import_map);
}
ty_str
}
fn resolve_syn_type(ty: &syn::Type, import_map: &HashMap<String, String>) -> String {
match ty {
syn::Type::Path(type_path) => resolve_type_path(type_path, import_map),
syn::Type::Tuple(tuple) => {
let resolved: Vec<_> =
tuple.elems.iter().map(|elem| resolve_syn_type(elem, import_map)).collect();
format!("({})", resolved.join(", "))
}
syn::Type::Reference(reference) => {
let resolved = resolve_syn_type(&reference.elem, import_map);
if reference.mutability.is_some() {
format!("&mut {resolved}")
} else {
format!("&{resolved}")
}
}
_ => quote::quote!(#ty).to_string(),
}
}
fn resolve_type_path(type_path: &syn::TypePath, import_map: &HashMap<String, String>) -> String {
let path = &type_path.path;
let segments: Vec<_> = path.segments.iter().collect();
if segments.is_empty() {
return quote::quote!(#path).to_string();
}
let first_seg = &segments[0];
let first_name = first_seg.ident.to_string();
if let Some(resolved_base) = import_map.get(&first_name) {
let rest: Vec<String> =
segments[1..].iter().map(|seg| format_segment(seg, import_map)).collect();
if rest.is_empty() {
let generics = format_generic_args(&first_seg.arguments, import_map);
format!("{resolved_base}{generics}")
} else {
format!("{}::{}", resolved_base, rest.join("::"))
}
} else {
let formatted: Vec<String> =
segments.iter().map(|seg| format_segment(seg, import_map)).collect();
formatted.join("::")
}
}
fn format_segment(seg: &syn::PathSegment, import_map: &HashMap<String, String>) -> String {
let name = seg.ident.to_string();
let generics = format_generic_args(&seg.arguments, import_map);
format!("{name}{generics}")
}
fn format_generic_args(
args: &syn::PathArguments,
import_map: &HashMap<String, String>,
) -> String {
match args {
syn::PathArguments::None => String::new(),
syn::PathArguments::AngleBracketed(angle) => {
let resolved: Vec<String> = angle
.args
.iter()
.map(|arg| match arg {
syn::GenericArgument::Type(ty) => resolve_syn_type(ty, import_map),
other => quote::quote!(#other).to_string(),
})
.collect();
format!("<{}>", resolved.join(", "))
}
syn::PathArguments::Parenthesized(paren) => {
let inputs: Vec<String> =
paren.inputs.iter().map(|ty| resolve_syn_type(ty, import_map)).collect();
let output = match &paren.output {
syn::ReturnType::Default => String::new(),
syn::ReturnType::Type(_, ty) => format!(" -> {}", resolve_syn_type(ty, import_map)),
};
format!("({}){}", inputs.join(", "), output)
}
}
}
fn resolve_path_string(path: &str, import_map: &HashMap<String, String>) -> String {
let segments: Vec<&str> = path.split("::").collect();
if segments.is_empty() {
return path.to_string();
}
if let Some(resolved_base) = import_map.get(segments[0]) {
if segments.len() == 1 {
resolved_base.clone()
} else {
format!("{}::{}", resolved_base, segments[1..].join("::"))
}
} else {
path.to_string()
}
}
pub(crate) fn build_type_map(
imports: &[ImportInfo],
functions: &[crate::FunctionInfo],
events: &[crate::EventInfo],
) -> TypeMap {
let import_map = build_import_map(imports);
let mut type_map = TypeMap::new();
for func in functions {
let input_key = func.input_type.to_string();
let input_resolved = resolve_type(&func.input_type, &import_map);
type_map.insert(input_key, input_resolved);
let output_key = func.output_type.to_string();
let output_resolved = resolve_type(&func.output_type, &import_map);
type_map.insert(output_key, output_resolved);
if let Some(feed_type) = &func.feed_type {
let feed_key = feed_type.to_string();
let feed_resolved = resolve_type(feed_type, &import_map);
type_map.insert(feed_key, feed_resolved);
}
}
for event in events {
let data_key = event.data_type.to_string();
let data_resolved = resolve_type(&event.data_type, &import_map);
type_map.insert(data_key, data_resolved);
let topic_resolved = resolve_path_string(&event.topic, &import_map);
type_map.insert(event.topic.clone(), topic_resolved);
}
type_map
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
fn make_import(name: &str, path: &str) -> ImportInfo {
ImportInfo { name: name.to_string(), path: path.to_string() }
}
#[test]
fn test_resolve_simple_type() {
let imports = vec![make_import("Deposit", "evm_core::standard_bridge::Deposit")];
let import_map = build_import_map(&imports);
let ty = quote! { Deposit };
let resolved = resolve_type(&ty, &import_map);
assert_eq!(resolved, "evm_core::standard_bridge::Deposit");
}
#[test]
fn test_resolve_aliased_type() {
let imports = vec![make_import("DSAddress", "evm_core::Address")];
let import_map = build_import_map(&imports);
let ty = quote! { DSAddress };
let resolved = resolve_type(&ty, &import_map);
assert_eq!(resolved, "evm_core::Address");
}
#[test]
fn test_resolve_multi_segment_path() {
let imports = vec![make_import("events", "evm_core::standard_bridge::events")];
let import_map = build_import_map(&imports);
let ty = quote! { events::PauseToggled };
let resolved = resolve_type(&ty, &import_map);
assert_eq!(resolved, "evm_core::standard_bridge::events::PauseToggled");
}
#[test]
fn test_resolve_generic_type() {
let imports = vec![make_import("Deposit", "evm_core::standard_bridge::Deposit")];
let import_map = build_import_map(&imports);
let ty = quote! { Option<Deposit> };
let resolved = resolve_type(&ty, &import_map);
assert_eq!(resolved, "Option<evm_core::standard_bridge::Deposit>");
}
#[test]
fn test_resolve_tuple_type() {
let imports = vec![
make_import("Deposit", "evm_core::standard_bridge::Deposit"),
make_import("DSAddress", "evm_core::Address"),
];
let import_map = build_import_map(&imports);
let ty = quote! { (Deposit, DSAddress) };
let resolved = resolve_type(&ty, &import_map);
assert_eq!(resolved, "(evm_core::standard_bridge::Deposit, evm_core::Address)");
}
#[test]
fn test_resolve_unit_type() {
let imports = vec![];
let import_map = build_import_map(&imports);
let ty = quote! { () };
let resolved = resolve_type(&ty, &import_map);
assert_eq!(resolved, "()");
}
#[test]
fn test_resolve_primitive_unchanged() {
let imports = vec![];
let import_map = build_import_map(&imports);
let ty = quote! { u64 };
let resolved = resolve_type(&ty, &import_map);
assert_eq!(resolved, "u64");
}
}