use syn::visit::Visit;
use super::{BareFallback, UseMap, resolve_path, strip_raw};
#[derive(Default)]
pub(crate) struct PathCollector {
pub(crate) paths: Vec<syn::Path>,
}
impl<'ast> Visit<'ast> for PathCollector {
fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
self.paths.push(node.path.clone());
syn::visit::visit_type_path(self, node);
}
fn visit_trait_bound(&mut self, node: &'ast syn::TraitBound) {
self.paths.push(node.path.clone());
syn::visit::visit_trait_bound(self, node);
}
}
pub(crate) struct ShapeExposure {
pub(crate) shape: String,
pub(crate) principal: Option<syn::Path>,
pub(crate) seam: String,
}
pub(crate) fn stamp_seam(mut exposures: Vec<ShapeExposure>, seam: &str) -> Vec<ShapeExposure> {
for exposure in &mut exposures {
exposure.seam = seam.to_string();
}
exposures
}
#[derive(Default)]
pub(crate) struct DynCollector {
pub(crate) exposures: Vec<ShapeExposure>,
}
impl<'ast> Visit<'ast> for DynCollector {
fn visit_type_trait_object(&mut self, node: &'ast syn::TypeTraitObject) {
self.exposures.push(ShapeExposure {
shape: trait_object_to_string(node),
principal: principal_trait_path(&node.bounds),
seam: String::new(),
});
syn::visit::visit_type_trait_object(self, node);
}
}
fn principal_trait_path(
bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus>,
) -> Option<syn::Path> {
bounds.iter().find_map(|bound| match bound {
syn::TypeParamBound::Trait(trait_bound) => Some(trait_bound.path.clone()),
_ => None,
})
}
fn trait_object_to_string(node: &syn::TypeTraitObject) -> String {
let parts: Vec<String> = node.bounds.iter().map(bound_to_string).collect();
format!("dyn {}", parts.join(" + "))
}
fn impl_trait_to_string(node: &syn::TypeImplTrait) -> String {
let parts: Vec<String> = node.bounds.iter().map(bound_to_string).collect();
format!("impl {}", parts.join(" + "))
}
#[derive(Default)]
pub(crate) struct ImplTraitCollector {
pub(crate) exposures: Vec<ShapeExposure>,
}
impl<'ast> Visit<'ast> for ImplTraitCollector {
fn visit_type_impl_trait(&mut self, node: &'ast syn::TypeImplTrait) {
self.exposures.push(ShapeExposure {
shape: impl_trait_to_string(node),
principal: principal_trait_path(&node.bounds),
seam: String::new(),
});
syn::visit::visit_type_impl_trait(self, node);
}
}
fn bound_to_string(bound: &syn::TypeParamBound) -> String {
match bound {
syn::TypeParamBound::Trait(tb) => {
let modifier = match tb.modifier {
syn::TraitBoundModifier::Maybe(_) => "?",
_ => "",
};
let path = path_to_string(&tb.path).unwrap_or_else(|| "_".to_string());
format!("{modifier}{path}")
}
syn::TypeParamBound::Lifetime(lt) => format!("'{}", strip_raw(<.ident.to_string())),
_ => "_".to_string(),
}
}
fn expr_to_string(expr: &syn::Expr) -> Option<String> {
match expr {
syn::Expr::Lit(lit) => match &lit.lit {
syn::Lit::Int(i) => Some(i.base10_digits().to_string()),
syn::Lit::Bool(b) => Some(b.value.to_string()),
syn::Lit::Char(c) => Some(format!("{:?}", c.value())),
syn::Lit::Str(s) => Some(format!("{:?}", s.value())),
_ => None,
},
syn::Expr::Path(p) => path_to_string(&p.path),
_ => None,
}
}
fn generic_argument_to_string(arg: &syn::GenericArgument) -> Option<String> {
match arg {
syn::GenericArgument::Type(ty) => type_to_string(ty),
syn::GenericArgument::Lifetime(lt) => {
Some(format!("'{}", strip_raw(<.ident.to_string())))
}
syn::GenericArgument::AssocType(binding) => Some(format!(
"{} = {}",
strip_raw(&binding.ident.to_string()),
type_to_string(&binding.ty)?
)),
syn::GenericArgument::AssocConst(binding) => Some(format!(
"{} = {}",
strip_raw(&binding.ident.to_string()),
expr_to_string(&binding.value)?
)),
syn::GenericArgument::Const(expr) => expr_to_string(expr),
syn::GenericArgument::Constraint(c) => {
let bounds: Vec<String> = c.bounds.iter().map(bound_to_string).collect();
Some(format!(
"{}: {}",
strip_raw(&c.ident.to_string()),
bounds.join(" + ")
))
}
_ => None,
}
}
pub(crate) fn type_to_string(ty: &syn::Type) -> Option<String> {
match ty {
syn::Type::Path(tp) => {
let mut out = String::new();
if let Some(qself) = &tp.qself {
out.push('<');
out.push_str(&type_to_string(&qself.ty)?);
out.push('>');
out.push_str("::");
}
out.push_str(&path_to_string(&tp.path)?);
Some(out)
}
syn::Type::Reference(r) => {
let inner = type_to_string(&r.elem)?;
if r.mutability.is_some() {
Some(format!("&mut {inner}"))
} else {
Some(format!("&{inner}"))
}
}
syn::Type::Tuple(t) => {
let parts: Option<Vec<String>> = t.elems.iter().map(type_to_string).collect();
Some(format!("({})", parts?.join(", ")))
}
syn::Type::Slice(s) => Some(format!("[{}]", type_to_string(&s.elem)?)),
syn::Type::Array(a) => Some(format!("[{}; _]", type_to_string(&a.elem)?)),
syn::Type::Group(g) => type_to_string(&g.elem),
syn::Type::Paren(p) => type_to_string(&p.elem),
syn::Type::TraitObject(t) => Some(trait_object_to_string(t)),
syn::Type::Ptr(p) => {
let inner = type_to_string(&p.elem)?;
if p.mutability.is_some() {
Some(format!("*mut {inner}"))
} else {
Some(format!("*const {inner}"))
}
}
syn::Type::Never(_) => Some("!".to_string()),
syn::Type::Macro(m) => Some(format!("{}!", path_to_string(&m.mac.path)?)),
syn::Type::BareFn(f) => {
let inputs: Option<Vec<String>> =
f.inputs.iter().map(|a| type_to_string(&a.ty)).collect();
let output = match &f.output {
syn::ReturnType::Default => String::new(),
syn::ReturnType::Type(_, ty) => format!(" -> {}", type_to_string(ty)?),
};
Some(format!("fn({}){output}", inputs?.join(", ")))
}
_ => None,
}
}
pub(crate) fn canonical_self_owner(
self_ty: &syn::Type,
uses: &UseMap,
module: &str,
ordinal: usize,
) -> String {
if let syn::Type::Path(tp) = self_ty {
if tp.qself.is_none() {
if let Some(base) = resolve_path(&tp.path, uses, module, BareFallback::CurrentModule) {
return match render_last_segment_args(&tp.path) {
Some(args) => format!("{base}{args}"),
None => format!("{base}<_#{ordinal}>"),
};
}
}
}
type_to_string(self_ty).unwrap_or_else(|| format!("_#{ordinal}"))
}
fn render_last_segment_args(path: &syn::Path) -> Option<String> {
match &path.segments.last()?.arguments {
syn::PathArguments::None => Some(String::new()),
syn::PathArguments::AngleBracketed(args) => {
let rendered: Option<Vec<String>> =
args.args.iter().map(generic_argument_to_string).collect();
Some(format!("<{}>", rendered?.join(", ")))
}
syn::PathArguments::Parenthesized(_) => None,
}
}
pub(crate) fn path_to_string(path: &syn::Path) -> Option<String> {
let mut segs = Vec::with_capacity(path.segments.len());
if path.leading_colon.is_some() {
segs.push(String::new());
}
for seg in &path.segments {
let ident = strip_raw(&seg.ident.to_string());
match &seg.arguments {
syn::PathArguments::None => segs.push(ident),
syn::PathArguments::AngleBracketed(args) => {
let rendered: Option<Vec<String>> =
args.args.iter().map(generic_argument_to_string).collect();
segs.push(format!("{ident}<{}>", rendered?.join(", ")));
}
syn::PathArguments::Parenthesized(args) => {
let inputs: Option<Vec<String>> = args.inputs.iter().map(type_to_string).collect();
let output = match &args.output {
syn::ReturnType::Default => String::new(),
syn::ReturnType::Type(_, ty) => format!(" -> {}", type_to_string(ty)?),
};
segs.push(format!("{ident}({}){output}", inputs?.join(", ")));
}
}
}
Some(segs.join("::"))
}