spread_macros 0.2.0

Macros around an extended spread syntax
Documentation
use {
    crate::{common::*, *},
    syn::bracketed,
};

pub fn assert_fields_eq(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let assert_fields_eq = parse_macro_input!(tokens as AssertFieldsEq);

    match assert_fields_eq {
        AssertFieldsEq::List {
            left,
            right,
            fields,
            fmt_args,
        } => {
            let fields: Vec<_> = fields.into_iter().collect();
            quote! {
                {
                    #[allow(non_camel_case_types)]
                    #[derive(Debug, PartialEq, Eq)]
                    struct Fields
                    <
                        'a,
                        #( #fields, )*
                    > {
                        #(#fields: &'a #fields,)*
                    }

                    let left = &#left;
                    let left = Fields {
                        #( #fields: & (left . #fields) ,)*
                    };

                    let right = &#right;
                    let right = Fields {
                        #( #fields: & (right . #fields) ,)*
                    };

                    assert_eq!(left, right #fmt_args);
                }
            }
            .into()
        }
        AssertFieldsEq::Anon {
            left,
            anon,
            fmt_args,
        } => {
            let mut fields = vec![];

            for field in &anon.items {
                match field {
                    SpreadItem::Field(Field { name, .. }) => fields.push(name.clone()),
                    SpreadItem::SpreadList(list) => {
                        for field in list.fields_list.iter() {
                            fields.push(field.name.clone())
                        }
                    }
                    SpreadItem::FinalSpread(_, _) => {
                        unreachable!("FinalSpread is not allowed in anon!")
                    }
                }
            }

            let anon = anon.expand();

            quote! {
                {
                    let right = #anon;

                    #[allow(non_camel_case_types)]
                    #[derive(Debug, PartialEq, Eq)]
                    struct Fields
                    <
                        'a,
                        #( #fields, )*
                    > {
                        #(#fields: &'a #fields,)*
                    }

                    let left = &#left;
                    let left = Fields {
                        #( #fields: & (left . #fields) ,)*
                    };

                    let right = &right;
                    let right = Fields {
                        #( #fields: & (right . #fields) ,)*
                    };

                    assert_eq!(left, right #fmt_args);
                }
            }
            .into()
        }
    }
}

enum AssertFieldsEq {
    List {
        left: syn::Expr,
        right: syn::Expr,
        fields: Punctuated<syn::Ident, Token![,]>,
        fmt_args: TokenStream,
    },
    Anon {
        left: syn::Expr,
        anon: crate::anon::Anon,
        fmt_args: TokenStream,
    },
}

impl Parse for AssertFieldsEq {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let left = input.parse()?;
        let _: Token![,] = input.parse()?;

        let lookahead = input.lookahead1();
        if lookahead.peek(Brace) {
            let braced;
            braced!(braced in input);

            let anon = braced.parse()?;
            let fmt_args = input.parse()?;

            Ok(AssertFieldsEq::Anon {
                left,
                anon,
                fmt_args,
            })
        } else if lookahead.peek(syn::Ident) {
            let right = input.parse()?;
            let _: Token![,] = input.parse()?;

            let bracketed;
            let bracket = bracketed!(bracketed in input);

            let fields = Punctuated::parse_terminated(&bracketed)?;

            if fields.is_empty() {
                return Err(syn::Error::new(
                    bracket.span.join(),
                    "`Fields list cannot be empty",
                ));
            }

            let fmt_args = input.parse()?;

            Ok(AssertFieldsEq::List {
                left,
                right,
                fields,
                fmt_args,
            })
        } else {
            Err(lookahead.error())?
        }
    }
}