soapy_derive/
lib.rs

1//! This crate provides the derive macro for Soapy.
2
3mod fields;
4mod zst;
5
6use std::collections::HashSet;
7
8use fields::{fields_struct, FieldKind};
9use proc_macro::TokenStream;
10use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
11use quote::{quote, quote_spanned};
12use syn::{parse_macro_input, Attribute, Data, DeriveInput, Fields};
13use zst::{zst_struct, ZstKind};
14
15#[proc_macro_derive(Soapy, attributes(align, soa_derive))]
16pub fn soa(input: TokenStream) -> TokenStream {
17    let input: DeriveInput = parse_macro_input!(input);
18    let span = input.ident.span();
19    match soa_inner(input) {
20        Ok(tokens) => tokens,
21        Err(e) => match e {
22            SoapyError::NotAStruct => quote_spanned! {
23                span => compile_error!("Soapy only applies to structs");
24            },
25            SoapyError::Syn(e) => e.into_compile_error(),
26        },
27    }
28    .into()
29}
30
31fn soa_inner(input: DeriveInput) -> Result<TokenStream2, SoapyError> {
32    let DeriveInput {
33        ident,
34        vis,
35        data,
36        attrs,
37        generics: _,
38    } = input;
39
40    let soa_derive = SoaDerive::try_from(attrs)?;
41    match data {
42        Data::Struct(strukt) => match strukt.fields {
43            Fields::Named(fields) => Ok(fields_struct(
44                ident,
45                vis,
46                fields.named,
47                FieldKind::Named,
48                soa_derive,
49            )?),
50            Fields::Unnamed(fields) => Ok(fields_struct(
51                ident,
52                vis,
53                fields.unnamed,
54                FieldKind::Unnamed,
55                soa_derive,
56            )?),
57            Fields::Unit => Ok(zst_struct(ident, vis, ZstKind::Unit)),
58        },
59        Data::Enum(_) | Data::Union(_) => Err(SoapyError::NotAStruct),
60    }
61}
62
63#[derive(Debug, Clone)]
64enum SoapyError {
65    NotAStruct,
66    Syn(syn::Error),
67}
68
69impl From<syn::Error> for SoapyError {
70    fn from(value: syn::Error) -> Self {
71        Self::Syn(value)
72    }
73}
74
75#[derive(Debug, Clone, Default)]
76struct SoaDerive {
77    derives: HashSet<syn::Path>,
78}
79
80impl SoaDerive {
81    fn into_derive(self) -> TokenStream2 {
82        let Self { derives } = self;
83        let derives = derives.into_iter();
84        quote! {
85            #[derive(#(#derives),*)]
86        }
87    }
88
89    fn insert(&mut self, derive: &str) {
90        self.derives.insert(syn::Path::from(syn::PathSegment {
91            ident: Ident::new(derive, Span::call_site()),
92            arguments: syn::PathArguments::None,
93        }));
94    }
95}
96
97impl TryFrom<Vec<Attribute>> for SoaDerive {
98    type Error = syn::Error;
99
100    fn try_from(value: Vec<Attribute>) -> Result<Self, Self::Error> {
101        let mut out = Self::default();
102        for attr in value {
103            if attr.path().is_ident("soa_derive") {
104                let _ = attr.parse_nested_meta(|meta| {
105                    out.derives.insert(meta.path);
106                    Ok(())
107                });
108            }
109        }
110        Ok(out)
111    }
112}