miniconf_derive 0.21.0

Derive macros for `miniconf`
Documentation
use std::collections::BTreeMap;

use darling::{
    FromField, FromMeta,
    usage::{IdentSet, Purpose, UsesTypeParams},
    uses_lifetimes, uses_type_params,
    util::{Flag, Override, SpannedValue},
};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::{parse_quote_spanned, spanned::Spanned};

#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum TreeTrait {
    Schema,
    Serialize,
    Deserialize,
    Any,
}

pub(crate) type MetaMap = BTreeMap<syn::Ident, Override<SpannedValue<String>>>;

#[derive(Debug, FromField, Clone)]
#[darling(attributes(tree), forward_attrs(doc))]
pub(crate) struct TreeField {
    pub ident: Option<syn::Ident>,
    ty: syn::Type,
    pub skip: Flag,
    typ: Option<syn::Type>, // Type to defer to
    rename: Option<syn::Ident>,
    defer: Option<syn::Expr>, // Value to defer to
    #[darling(default)]
    with: Option<syn::Path>,
    #[darling(default)]
    bounds: Bounds,
    #[darling(default)]
    pub meta: MetaMap,
    pub attrs: Vec<syn::Attribute>,
}

uses_type_params!(TreeField, ty, typ);
uses_lifetimes!(TreeField, ty, typ);

#[derive(Debug, Default, FromMeta, Clone)]
struct Bounds {
    schema: Option<Vec<syn::WherePredicate>>,
    serialize: Option<Vec<syn::WherePredicate>>,
    deserialize: Option<Vec<syn::WherePredicate>>,
    any: Option<Vec<syn::WherePredicate>>,
}

impl TreeField {
    pub fn span(&self) -> Span {
        self.ident
            .as_ref()
            .map(|i| i.span())
            .unwrap_or(self.ty.span())
    }

    fn typ(&self) -> &syn::Type {
        self.typ.as_ref().unwrap_or(&self.ty)
    }

    pub fn schema(&self) -> TokenStream {
        if let Some(all) = self.with.as_ref() {
            let typ = self.typ();
            quote_spanned!(self.span()=> #all::schema::<#typ>())
        } else {
            let typ = self.typ();
            quote_spanned!(self.span()=> <#typ as ::miniconf::TreeSchema>::SCHEMA)
        }
    }

    pub fn bound(&self, trtr: TreeTrait, type_set: &IdentSet) -> Option<TokenStream> {
        if let Some(bounds) = match trtr {
            TreeTrait::Schema => &self.bounds.schema,
            TreeTrait::Serialize => &self.bounds.serialize,
            TreeTrait::Deserialize => &self.bounds.deserialize,
            TreeTrait::Any => &self.bounds.any,
        } {
            Some(bounds.iter().map(|b| quote!(#b, )).collect())
        } else if self
            .uses_type_params(&Purpose::BoundImpl.into(), type_set)
            .is_empty()
            || self.with.is_some()
        {
            None
        } else {
            let ty = self.typ();
            Some(match trtr {
                TreeTrait::Schema => quote_spanned!(self.span()=> #ty: ::miniconf::TreeSchema,),
                TreeTrait::Serialize => {
                    quote_spanned!(self.span()=> #ty: ::miniconf::TreeSerialize,)
                }
                TreeTrait::Deserialize => {
                    quote_spanned!(self.span()=> #ty: ::miniconf::TreeDeserialize<'__de>,)
                }
                TreeTrait::Any => quote_spanned!(self.span()=> #ty: ::miniconf::TreeAny,),
            })
        }
    }

    pub fn name(&self) -> Option<&syn::Ident> {
        self.rename.as_ref().or(self.ident.as_ref())
    }

    fn value(&self, i: Option<usize>) -> syn::Expr {
        let def = if let Some(i) = i {
            // named or tuple struct field
            if let Some(name) = &self.ident {
                parse_quote_spanned!(self.span()=> self.#name)
            } else {
                let index = syn::Index::from(i);
                parse_quote_spanned!(self.span()=> self.#index)
            }
        } else {
            // enum variant newtype value
            parse_quote_spanned!(self.span()=> (*value))
        };
        self.defer.clone().unwrap_or(def)
    }

    pub fn serialize_by_key(&self, i: Option<usize>) -> TokenStream {
        // Quote context is a match of the field index with `serialize_by_key()` args available.
        let value = self.value(i);
        let imp = self
            .with
            .as_ref()
            .map(|m| quote!(#m::serialize_by_key(&#value, keys, ser)))
            .unwrap_or(quote!(#value.serialize_by_key(keys, ser)));
        quote_spanned! { self.span()=> #imp }
    }

    pub fn deserialize_by_key(&self, i: Option<usize>) -> TokenStream {
        // Quote context is a match of the field index with `deserialize_by_key()` args available.
        let value = self.value(i);
        let imp = self
            .with
            .as_ref()
            .map(|m| quote!(#m::deserialize_by_key(&mut #value, keys, de)))
            .unwrap_or(quote!(#value.deserialize_by_key(keys, de)));
        quote_spanned! { self.span()=> #imp }
    }

    pub fn probe_by_key(&self, i: usize) -> TokenStream {
        // Quote context is a match of the field index with `probe_by_key()` args available.
        let typ = self.typ();
        let imp = self
            .with
            .as_ref()
            .map(|m| quote!(#m::probe_by_key::<#typ, _>(keys, de)))
            .unwrap_or(
                quote!(<#typ as ::miniconf::TreeDeserialize::<'__de>>::probe_by_key(keys, de)),
            );
        quote_spanned! { self.span()=> #i => #imp }
    }

    pub fn ref_any_by_key(&self, i: Option<usize>) -> TokenStream {
        // Quote context is a match of the field index with `get_mut_by_key()` args available.
        let value = self.value(i);
        let imp = self
            .with
            .as_ref()
            .map(|m| quote!(#m::ref_any_by_key(&#value, keys)))
            .unwrap_or(quote!(#value.ref_any_by_key(keys)));
        quote_spanned! { self.span()=> #imp }
    }

    pub fn mut_any_by_key(&self, i: Option<usize>) -> TokenStream {
        // Quote context is a match of the field index with `get_mut_by_key()` args available.
        let value = self.value(i);
        let imp = self
            .with
            .as_ref()
            .map(|m| quote!(#m::mut_any_by_key(&mut #value, keys)))
            .unwrap_or(quote!(#value.mut_any_by_key(keys)));
        quote_spanned! { self.span()=> #imp }
    }
}