1mod 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}