use proc_macro2::TokenStream;
use quote::ToTokens;
use quote::quote;
use std::collections::HashMap;
use syn::GenericParam;
use syn::Generics;
use syn::Ident;
use syn::Token;
use syn::TypeParamBound;
use syn::punctuated::Punctuated;
pub fn parse_bounds(
name: &Ident,
bounds: &Punctuated<TypeParamBound, Token![+]>,
) -> Vec<TokenStream> {
bounds
.iter()
.filter_map(|st| match st {
TypeParamBound::Trait(trait_bound) => {
let trait_name = &trait_bound
.path
.segments
.last()
.unwrap_or_else(|| panic!("{name}: Expected trait segment"))
.ident;
Some(quote! { stringify!(#trait_name) })
}
TypeParamBound::Lifetime(_) => None,
TypeParamBound::PreciseCapture(_) => {
panic!("{name}: PreciseCapture not supported")
}
TypeParamBound::Verbatim(_) => panic!("{name}: Verbatim not supported"),
_ => None,
})
.collect()
}
pub fn parse_generics(name: &Ident, generics: &Generics) -> Vec<TokenStream> {
let where_map = if let Some(where_clause) = &generics.where_clause {
parse_where_predicates(name, &where_clause.predicates)
} else {
HashMap::new()
};
parse_generic_params(name, &generics.params, where_map)
}
pub fn list_generics(generics: &Generics) -> Vec<String> {
generics
.params
.iter()
.filter_map(|param| match param {
syn::GenericParam::Type(type_param) => Some(type_param.ident.to_string()),
_ => None,
})
.collect()
}
fn parse_where_predicates(
name: &Ident,
predicates: &Punctuated<syn::WherePredicate, Token![,]>,
) -> HashMap<String, Vec<TokenStream>> {
let mut map = HashMap::new();
predicates.iter().for_each(|pred| match pred {
syn::WherePredicate::Type(type_pred) => {
let type_name = &type_pred.bounded_ty;
let bounds = parse_bounds(name, &type_pred.bounds);
let name_str = type_name.to_token_stream().to_string();
map.insert(name_str, bounds);
}
syn::WherePredicate::Lifetime(_) => {}
_ => {}
});
map
}
fn parse_generic_params(
name: &Ident,
generics: &Punctuated<GenericParam, Token![,]>,
where_map: HashMap<String, Vec<TokenStream>>,
) -> Vec<TokenStream> {
generics
.iter()
.filter_map(|param| match param {
syn::GenericParam::Lifetime(_) => None,
syn::GenericParam::Type(type_param) => {
let type_name = &type_param.ident;
let type_name_str = type_name.to_token_stream().to_string();
let bounds = if let Some(where_bounds) = where_map.get(&type_name_str) {
where_bounds
} else {
&parse_bounds(name, &type_param.bounds)
};
Some(quote! {
::agdb::api_def::Generic {
name: stringify!(#type_name),
bounds: &[#(#bounds),*],
}
})
}
syn::GenericParam::Const(const_param) => panic!(
"{name}: Const generic parameters are not supported: {}",
const_param.ident
),
})
.collect()
}