rama-macros 0.2.0-alpha.2

procedural macross for rama
Documentation
use proc_macro2::{Ident, TokenStream};
use quote::quote_spanned;
use syn::{
    parse::{Parse, ParseStream},
    spanned::Spanned,
    Field, ItemStruct, Token,
};

use crate::{
    attr_parsing::{combine_unary_attribute, parse_attrs, Combine},
    type_parsing::extract_type_from_arc,
};

pub(crate) fn expand(item: ItemStruct) -> syn::Result<TokenStream> {
    if !item.generics.params.is_empty() {
        return Err(syn::Error::new_spanned(
            item.generics,
            "`#[derive(AsRef)]` doesn't support generics",
        ));
    }

    let tokens = item
        .fields
        .iter()
        .enumerate()
        .map(|(idx, field)| expand_field(&item.ident, idx, field))
        .collect();

    Ok(tokens)
}

fn expand_field(state: &Ident, idx: usize, field: &Field) -> TokenStream {
    let FieldAttrs { skip, wrap } = match parse_attrs("as_ref", &field.attrs) {
        Ok(attrs) => attrs,
        Err(err) => return err.into_compile_error(),
    };

    if skip.is_some() {
        return TokenStream::default();
    }

    let field_ty = &field.ty;
    let span = field.ty.span();

    let body = if let Some(field_ident) = &field.ident {
        quote_spanned! {span=> &self.#field_ident }
    } else {
        let idx = syn::Index {
            index: idx as _,
            span: field.span(),
        };
        quote_spanned! {span=> &self.#idx }
    };

    if wrap.is_some() {
        return match extract_type_from_arc(field_ty) {
            Some(field_ty) => {
                quote_spanned! {span=>
                    #[allow(clippy::needless_borrow)]
                    impl<T> ::std::convert::AsRef<T> for #state
                        where #field_ty: ::std::convert::AsRef<T>
                    {
                        fn as_ref(&self) -> &T {
                            use ::core::ops::Deref;
                            #body.deref().as_ref()
                        }
                    }
                }
            }
            None => syn::Error::new_spanned(
                field.ty.clone(),
                "`#[as_ref(wrap)]` is only supported for Arc types",
            )
            .into_compile_error(),
        };
    }

    quote_spanned! {span=>
        impl ::std::convert::AsRef<#field_ty> for #state {
            fn as_ref(&self) -> &#field_ty {
                #body
            }
        }
    }
}

mod kw {
    syn::custom_keyword!(skip);
    syn::custom_keyword!(wrap);
}

#[derive(Default)]
pub(super) struct FieldAttrs {
    pub(super) skip: Option<kw::skip>,
    pub(super) wrap: Option<kw::wrap>,
}

impl Parse for FieldAttrs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut skip = None;
        let mut wrap = None;

        while !input.is_empty() {
            let lh = input.lookahead1();
            if lh.peek(kw::skip) {
                skip = Some(input.parse()?);
            } else if lh.peek(kw::wrap) {
                wrap = Some(input.parse()?);
            } else {
                return Err(lh.error());
            }

            let _ = input.parse::<Token![,]>();
        }

        Ok(Self { skip, wrap })
    }
}

impl Combine for FieldAttrs {
    fn combine(mut self, other: Self) -> syn::Result<Self> {
        let Self { skip, wrap } = other;
        combine_unary_attribute(&mut self.skip, skip)?;
        combine_unary_attribute(&mut self.wrap, wrap)?;
        Ok(self)
    }
}

#[test]
fn ui() {
    crate::run_ui_tests("as_ref");
}