Skip to main content

wowlab_engine_macros_impl/
lib.rs

1//! Parsing, validation, and token generation for `WoW` Lab's engine proc macros.
2
3macro_rules! attr_keys {
4    (
5        $meta:ident,
6        unknown = $unknown:expr;
7        $(
8            $key:literal => ($duplicate:expr, $duplicate_error:expr) $body:block
9        ),+ $(,)?
10    ) => {
11        if false {
12            unreachable!()
13        }
14        $(
15            else if $meta.path.is_ident($key) {
16                if $duplicate {
17                    Err(syn::Error::new_spanned(
18                        &$meta.path,
19                        $duplicate_error,
20                    ))
21                } else {
22                    $body
23                    Ok(())
24                }
25            }
26        )+
27        else {
28            Err(syn::Error::new_spanned(&$meta.path, $unknown))
29        }
30    };
31}
32
33mod codegen;
34mod copy_insert;
35mod copy_insert_parse;
36mod lower;
37mod lower_parse;
38mod parse;
39mod resolved_store;
40mod spec_handler_delegation;
41
42use proc_macro2::TokenStream;
43use syn::DeriveInput;
44
45struct DefineErrorInput {
46    wrapper: syn::ItemStruct,
47    kind: syn::ItemEnum,
48}
49
50impl syn::parse::Parse for DefineErrorInput {
51    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
52        let first: syn::Item = input.parse()?;
53        let second: syn::Item = input.parse()?;
54
55        if !input.is_empty() {
56            return Err(input.error("define_error! expects one wrapper struct and one kind enum"));
57        }
58
59        match (first, second) {
60            (syn::Item::Struct(wrapper), syn::Item::Enum(kind))
61            | (syn::Item::Enum(kind), syn::Item::Struct(wrapper)) => Ok(Self { wrapper, kind }),
62            (first, second) => Err(syn::Error::new_spanned(
63                quote::quote!(#first #second),
64                "define_error! expects one wrapper struct and one kind enum",
65            )),
66        }
67    }
68}
69
70/// Expand an opaque error wrapper and its private kind enum.
71#[must_use]
72pub fn define_error(input: TokenStream) -> TokenStream {
73    syn::parse2::<DefineErrorInput>(input)
74        .and_then(expand_define_error)
75        .unwrap_or_else(|error| error.to_compile_error())
76}
77
78fn expand_define_error(mut parsed: DefineErrorInput) -> syn::Result<TokenStream> {
79    let (field, delegates_kind_source) = match &mut parsed.wrapper.fields {
80        syn::Fields::Named(fields) => {
81            let field_count = fields.named.len();
82            let Some(kind_index) = fields
83                .named
84                .iter()
85                .position(|field| field.ident.as_ref().is_some_and(|ident| ident == "kind"))
86            else {
87                return Err(syn::Error::new_spanned(
88                    fields,
89                    "define_error! named wrapper must have a `kind` field",
90                ));
91            };
92            let kind_field = fields.named.get_mut(kind_index).ok_or_else(|| {
93                syn::Error::new(
94                    proc_macro2::Span::call_site(),
95                    "define_error! named wrapper must have a `kind` field",
96                )
97            })?;
98            let delegates_source = kind_field
99                .attrs
100                .iter()
101                .any(|attr| attr.path().is_ident("source"));
102            let field = kind_field
103                .ident
104                .clone()
105                .ok_or_else(|| syn::Error::new_spanned(&*kind_field, "field must be named"))?;
106
107            kind_field
108                .attrs
109                .retain(|attr| !attr.path().is_ident("source"));
110
111            (quote::quote!(#field), field_count != 1 || delegates_source)
112        }
113        syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => (quote::quote!(0), false),
114        _ => {
115            return Err(syn::Error::new_spanned(
116                &parsed.wrapper,
117                "define_error! wrapper must have one tuple field or a named `kind` field",
118            ));
119        }
120    };
121
122    let wrapper_ident = parsed.wrapper.ident.clone();
123    let wrapper_generics = parsed.wrapper.generics.clone();
124    let (impl_generics, type_generics, where_clause) = wrapper_generics.split_for_impl();
125    let impl_attrs: Vec<_> = parsed
126        .wrapper
127        .attrs
128        .iter()
129        .filter(|attr| attr.path().is_ident("cfg"))
130        .cloned()
131        .collect();
132    let source = if delegates_kind_source {
133        quote::quote!(::core::option::Option::Some(&self.#field))
134    } else {
135        quote::quote!(::std::error::Error::source(&self.#field))
136    };
137    let wrapper = parsed.wrapper;
138    let kind = parsed.kind;
139
140    Ok(quote::quote! {
141        #wrapper
142        #kind
143
144        #(#impl_attrs)*
145        impl #impl_generics ::core::fmt::Display for #wrapper_ident #type_generics #where_clause {
146            fn fmt(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
147                ::core::fmt::Display::fmt(&self.#field, formatter)
148            }
149        }
150
151        #(#impl_attrs)*
152        impl #impl_generics ::std::error::Error for #wrapper_ident #type_generics #where_clause {
153            fn source(&self) -> ::core::option::Option<&(dyn ::std::error::Error + 'static)> {
154                #source
155            }
156        }
157    })
158}
159
160/// Expand the `define_slot!` dense-buffer slot declaration.
161#[must_use]
162pub fn define_slot(input: TokenStream) -> TokenStream {
163    syn::parse2::<parse::DefineSlotInput>(input)
164        .and_then(|parsed| codegen::expand_define_slot(&parsed.item, parsed.impl_body.as_ref()))
165        .unwrap_or_else(|error| error.to_compile_error())
166}
167
168/// Expand `#[derive(HasId)]` into the configured DBC ID-trait implementation.
169#[must_use]
170pub fn derive_has_id(input: TokenStream) -> TokenStream {
171    expand_derive(input, codegen::expand_has_id)
172}
173
174/// Expand `#[derive(HasFk)]` into the configured DBC foreign-key trait implementation.
175#[must_use]
176pub fn derive_has_fk(input: TokenStream) -> TokenStream {
177    expand_derive(input, codegen::expand_has_fk)
178}
179
180/// Expand `#[derive(LowerData)]` into builder-to-runtime and optional runtime-to-info lowering.
181#[must_use]
182pub fn derive_lower_data(input: TokenStream) -> TokenStream {
183    expand_derive(input, lower::expand_lower_data)
184}
185
186/// Expand `#[derive(CopyInsert)]` into COPY column metadata and row binding code.
187#[must_use]
188pub fn derive_copy_insert(input: TokenStream) -> TokenStream {
189    expand_derive(input, copy_insert::expand_copy_insert)
190}
191
192/// Expand `#[derive(ResolvedStore)]` into a field-driven `Default` implementation.
193#[must_use]
194pub fn derive_resolved_store(input: TokenStream) -> TokenStream {
195    expand_derive(input, resolved_store::expand_resolved_store)
196}
197
198/// Expand the `SpecHandler` trait and its definition-derived forwarding macro.
199#[must_use]
200pub fn spec_handler_delegation(attribute: TokenStream, item: TokenStream) -> TokenStream {
201    syn::parse2::<syn::ItemTrait>(item)
202        .and_then(|item| spec_handler_delegation::expand_spec_handler_delegation(attribute, item))
203        .unwrap_or_else(|error| error.to_compile_error())
204}
205
206fn expand_derive(
207    input: TokenStream,
208    expand: impl FnOnce(&DeriveInput) -> syn::Result<TokenStream>,
209) -> TokenStream {
210    syn::parse2::<DeriveInput>(input)
211        .and_then(|parsed| expand(&parsed))
212        .unwrap_or_else(|error| error.to_compile_error())
213}
214
215#[cfg(test)]
216mod define_error_tests {
217    use googletest::prelude::*;
218
219    use super::*;
220
221    #[gtest]
222    fn define_error_emits_opaque_wrapper_and_kind() -> Result<()> {
223        let output = define_error(quote::quote! {
224            pub struct ExampleError {
225                #[source]
226                kind: ExampleErrorKind,
227            }
228
229            #[derive(thiserror::Error)]
230            enum ExampleErrorKind {
231                #[error("bad value {0}")]
232                BadValue(i32),
233            }
234        });
235        let output = output.to_string();
236
237        verify_that!(output, contains_substring("pub struct ExampleError"))?;
238        verify_that!(output, contains_substring("enum ExampleErrorKind"))?;
239
240        verify_that!(output, contains_substring("std :: error :: Error"))
241    }
242
243    #[gtest]
244    fn define_error_supports_transparent_tuple_wrappers() -> Result<()> {
245        let output = define_error(quote::quote! {
246            #[derive(Debug)]
247            pub struct TupleError(TupleErrorKind);
248
249            #[derive(Debug, thiserror::Error)]
250            enum TupleErrorKind {
251                #[error("bad value")]
252                BadValue,
253            }
254        });
255        let rendered = output.to_string();
256
257        verify_true!(rendered.contains("Display :: fmt (& self . 0"))?;
258
259        verify_true!(rendered.contains("Error :: source (& self . 0)"))
260    }
261}