std140-macros 0.1.2

Procedural macros for the std140 crate.
Documentation
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Ident};

pub fn expand_repr_std140(input: &DeriveInput) -> Result<TokenStream, String> {
    if let Data::Struct(data) = &input.data {
        if has_other_repr(input) {
            return Err(
                "Cannot parse another #[repr] attribute on a struct marked with #[repr_std140]"
                    .to_string(),
            );
        }

        let mod_path = quote!(std140);
        let struct_name = &input.ident;
        let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

        let asserts = data.fields.iter().map(|field| {
            let ty = &field.ty;
            let span = field.span();

            quote_spanned!(span=> assert_repr_std140::<#ty> { marker: std::marker::PhantomData };)
        });

        let suffix = struct_name.to_string().trim_start_matches("r#").to_owned();
        let dummy_const = Ident::new(
            &format!("_IMPL_REPR_STD140_FOR_{}", suffix),
            Span::call_site(),
        );

        let asserts = quote! {
            struct assert_repr_std140<T> where T: #mod_path::ReprStd140 {
                marker: std::marker::PhantomData<T>
            }

            #(#asserts)*
        };

        let impl_std140_struct = quote! {
            #[automatically_derived]
            unsafe impl #impl_generics #mod_path::Std140Struct for #struct_name #ty_generics #where_clause {}
        };

        let generated = quote! {
            #[repr(C, align(16))]
            #input

            #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
            const #dummy_const: () = {
                #[allow(unknown_lints)]
                #[cfg_attr(feature = "cargo-clippy", allow(useless_attribute))]
                #[allow(rust_2018_idioms)]

                #asserts

                #impl_std140_struct
            };
        };

        Ok(generated)
    } else {
        Err("Cannot represent an enum or union as std140, only a struct.".to_string())
    }
}

fn has_other_repr(input: &DeriveInput) -> bool {
    input
        .attrs
        .iter()
        .any(|attr| attr.path.is_ident(&Ident::new("repr", Span::call_site())))
}