#![warn(missing_docs)]
extern crate proc_macro;
use self::proc_macro::TokenStream;
use proc_macro_crate::{FoundCrate, crate_name};
use quote::quote;
use syn::{DeriveInput, Fields, GenericParam, Generics, parse_macro_input, parse_quote};
fn firebae_cm_path() -> proc_macro2::TokenStream {
match crate_name("firebae-cm") {
Ok(FoundCrate::Name(name)) => {
let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
quote!(::#ident)
}
Ok(FoundCrate::Itself) | Err(_) => quote!(::firebae_cm),
}
}
fn add_trait_bounds(mut generics: Generics, krate: &proc_macro2::TokenStream) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(ref mut type_param) = *param {
type_param
.bounds
.push(parse_quote!(#krate::FirebaseMapValue));
}
}
generics
}
fn error(span: proc_macro2::Span, message: &str) -> proc_macro2::TokenStream {
syn::Error::new(span, message).into_compile_error()
}
fn is_option(ty: &syn::Type) -> bool {
let syn::Type::Path(tp) = ty else {
return false;
};
if tp.qself.is_some() {
return false;
}
let Some(segment) = tp.path.segments.last() else {
return false;
};
segment.ident == "Option"
&& matches!(
&segment.arguments,
syn::PathArguments::AngleBracketed(args) if args.args.len() == 1
)
}
#[proc_macro_derive(AsFirebaseMap)]
pub fn impl_as_firebase_map(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let span = ast.ident.span();
let name = &ast.ident;
let krate = firebae_cm_path();
let generics = add_trait_bounds(ast.generics, &krate);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let data = match ast.data {
syn::Data::Struct(data) => data,
_ => return error(span, "AsFirebaseMap should be called on a struct").into(),
};
let fields = match data.fields {
Fields::Named(fields) => fields.named,
_ => return error(span, "AsFirebaseMap only works on named fields").into(),
};
let inserts = fields.into_iter().map(|f| {
let name = f.ident.unwrap();
if is_option(&f.ty) {
quote! { if let Some(ref val) = self.#name { h.insert(stringify!(#name), val); } }
} else {
quote! { h.insert(stringify!(#name), &self.#name); }
}
});
TokenStream::from(quote! {
impl #impl_generics #krate::IntoFirebaseMap for #name #ty_generics #where_clause {
fn as_map(&self) -> #krate::FirebaseMap {
let mut h = #krate::FirebaseMap::new();
#(#inserts)*
h
}
}
})
}