macroific_macro 3.0.0

Proc macros for the macroific crate
use std::fmt::Write;

use proc_macro2::TokenStream;
use proc_macro2::{Delimiter, Group, Ident, Punct};
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
use syn::parse::{Parse, ParseStream};
use syn::{parse_macro_input, Generics};

pub use attr_options::AttrOptionsDerive;
use macroific_core::core_ext::{MacroificCoreIdentExt, MacroificCorePunctExt};
use macroific_core::elements::module_prefix::{OPTION, RESULT};
use macroific_core::elements::{GenericImpl, ModulePrefix};
use options::*;
pub use parse_option::ParseOptionDerive;

use crate::BaseTokenStream;

mod attr_options;
mod options;
mod parse_option;

const ATTR_NAME: &str = "attr_opts";

const PRIVATE: ModulePrefix<'static> = ModulePrefix::new(&["macroific", "attr_parse", "__private"]);
const BASE: ModulePrefix<'static> = ModulePrefix::new(&["macroific", "attr_parse"]);

trait Render {
    const TRAIT_NAME: &'static str;

    fn generics(&self) -> &Generics;
    fn ident(&self) -> &Ident;
    fn fields(&self) -> &Fields;

    fn render_empty_body(ending: Option<Group>) -> TokenStream;

    #[inline]
    fn impl_generics(&self) -> TokenStream {
        impl_generics(self.generics(), self.ident(), Self::TRAIT_NAME)
    }

    fn named_fields(&self) -> Result<&[Field], Option<Delimiter>> {
        match *self.fields() {
            Fields::Named(ref fields) => Ok(fields),
            Fields::Empty(delim) => Err(Some(delim)),
            Fields::Unit => Err(None),
        }
    }

    fn render_empty(&self, delimiter: Option<Delimiter>) -> TokenStream {
        let ending = empty_ending(delimiter);
        let mut tokens = self.impl_generics();

        tokens.append(Group::new(
            Delimiter::Brace,
            Self::render_empty_body(ending),
        ));

        tokens
    }
}

fn nones(fields: &[Field]) -> TokenStream {
    (0..fields.len())
        .map(move |idx| {
            let ident = field_ident_at(idx);
            quote! { let mut #ident = #OPTION::None; }
        })
        .collect()
}

fn unwraps<'a>(
    indexed_fields: impl Iterator<Item = IndexedFieldTuple<'a>>,
    span_arg_name: &impl ToTokens,
) -> Group {
    let body = indexed_fields.map(move |(option_var_name, field)| {
        let mut out = field.ident.to_token_stream();
        out.append(Punct::new_joint(':'));

        match field.opts.default {
            None | Some(DefaultOption::Implicit | DefaultOption::Explicit(true)) => {
                out.extend(quote! { #option_var_name.unwrap_or_default() });
            }
            Some(DefaultOption::Explicit(false)) => {
                let mut missing_field_err = String::from("Missing required attribute: ");
                if let Some(ref rename) = field.opts.rename {
                    write!(&mut missing_field_err, "{}", rename.token()).unwrap();
                } else {
                    write!(&mut missing_field_err, "{}", field.ident).unwrap();
                }

                out.extend(quote! { if let #OPTION::Some(v) = #option_var_name {
                    v
                } else {
                    return #RESULT::Err(::syn::Error::new(#span_arg_name, #missing_field_err));
                } });
            }
            Some(DefaultOption::Path(ref path)) => {
                out.extend(quote! { #option_var_name.unwrap_or_else(#path) });
            }
        }

        out.append(Punct::new_joint(','));

        out
    });

    Group::new(Delimiter::Brace, body.collect())
}

type IndexedFieldTuple<'a> = (Ident, &'a Field);

fn indexed_fields(fields: &[Field]) -> impl Iterator<Item = IndexedFieldTuple<'_>> + Clone {
    fields.iter().enumerate().map(move |(idx, field)| {
        let option_var_name = field_ident_at(idx);

        (option_var_name, field)
    })
}

fn empty_ending(delimiter: Option<Delimiter>) -> Option<Group> {
    delimiter.map(move |d| Group::new(d, TokenStream::new()))
}

pub fn run<T: Parse + ToTokens>(input: BaseTokenStream) -> BaseTokenStream {
    parse_macro_input!(input as T).into_token_stream().into()
}

fn impl_generics(generics: &Generics, ident: &Ident, trait_name: &str) -> TokenStream {
    let impl_trait = {
        let trait_name = Ident::create(trait_name);
        quote!(#BASE::#trait_name)
    };
    let mut tokens = quote!(#[automatically_derived]);

    GenericImpl::new(generics)
        .with_trait(impl_trait)
        .with_target(ident)
        .to_tokens(&mut tokens);

    tokens
}

fn field_ident_at(idx: usize) -> Ident {
    format_ident!("field{idx}")
}