use proc_macro::TokenStream;
use quote::ToTokens;
use quote::quote;
use syn::AngleBracketedGenericArguments;
use syn::GenericArgument;
use syn::Ident;
use syn::PathArguments;
use syn::Token;
use syn::Type;
use syn::parse::Parse;
use syn::parse::ParseStream;
struct ImplProtoIdentInput {
ty: Type,
}
impl Parse for ImplProtoIdentInput {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let ty: Type = input.parse()?;
let _ = input.parse::<Token![;]>();
Ok(Self { ty })
}
}
pub fn impl_proto_ident(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as ImplProtoIdentInput);
let ty = input.ty;
let mut params: Vec<Ident> = Vec::new();
collect_type_params(&ty, &mut params);
let mut dedup: Vec<Ident> = Vec::new();
for p in params {
if !dedup.iter().any(|x| x == &p) {
dedup.push(p);
}
}
let ty_str = ty.to_token_stream().to_string();
let expanded = if dedup.is_empty() {
quote! {
#[cfg(feature = "build-schemas")]
impl ::proto_rs::schemas::ProtoIdentifiable for #ty {
const PROTO_IDENT: ::proto_rs::schemas::ProtoIdent = ::proto_rs::schemas::ProtoIdent {
module_path: module_path!(),
name: stringify!(#ty),
proto_package_name: "",
proto_file_path: "",
proto_type: ::proto_rs::schemas::ProtoType::Message(stringify!(#ty)),
generics: &[],
};
const PROTO_TYPE: ::proto_rs::schemas::ProtoType = ::proto_rs::schemas::ProtoType::Message(stringify!(#ty));
}
#[cfg(feature = "build-schemas")]
const _: () = <#ty as ::proto_rs::schemas::ProtoIdentifiable>::_VALIDATOR;
}
} else {
quote! {
#[cfg(feature = "build-schemas")]
impl <#(#dedup),*> ::proto_rs::schemas::ProtoIdentifiable for #ty {
const PROTO_IDENT: ::proto_rs::schemas::ProtoIdent = ::proto_rs::schemas::ProtoIdent {
module_path: module_path!(),
name: stringify!(#ty),
proto_package_name: "",
proto_file_path: "",
proto_type: ::proto_rs::schemas::ProtoType::Message(stringify!(#ty)),
generics: &[],
};
const PROTO_TYPE: ::proto_rs::schemas::ProtoType = ::proto_rs::schemas::ProtoType::Message(stringify!(#ty));
}
}
};
let _ = ty_str;
expanded.into()
}
fn collect_type_params(ty: &Type, out: &mut Vec<Ident>) {
match ty {
Type::Path(tp) => {
for seg in &tp.path.segments {
if let PathArguments::AngleBracketed(ab) = &seg.arguments {
collect_angle_params(ab, out);
}
}
}
Type::Reference(tr) => collect_type_params(&tr.elem, out),
Type::Paren(tp) => collect_type_params(&tp.elem, out),
Type::Group(tg) => collect_type_params(&tg.elem, out),
_ => {}
}
}
fn collect_angle_params(ab: &AngleBracketedGenericArguments, out: &mut Vec<Ident>) {
for arg in &ab.args {
if let GenericArgument::Type(Type::Path(tp)) = arg {
if let Some(last) = tp.path.segments.last() {
if last.arguments.is_empty() && tp.path.segments.len() == 1 {
out.push(last.ident.clone());
} else {
for seg in &tp.path.segments {
if let PathArguments::AngleBracketed(inner) = &seg.arguments {
collect_angle_params(inner, out);
}
}
}
}
}
}
}