wowlab_engine_macros_impl/
lib.rs1macro_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#[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#[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#[must_use]
170pub fn derive_has_id(input: TokenStream) -> TokenStream {
171 expand_derive(input, codegen::expand_has_id)
172}
173
174#[must_use]
176pub fn derive_has_fk(input: TokenStream) -> TokenStream {
177 expand_derive(input, codegen::expand_has_fk)
178}
179
180#[must_use]
182pub fn derive_lower_data(input: TokenStream) -> TokenStream {
183 expand_derive(input, lower::expand_lower_data)
184}
185
186#[must_use]
188pub fn derive_copy_insert(input: TokenStream) -> TokenStream {
189 expand_derive(input, copy_insert::expand_copy_insert)
190}
191
192#[must_use]
194pub fn derive_resolved_store(input: TokenStream) -> TokenStream {
195 expand_derive(input, resolved_store::expand_resolved_store)
196}
197
198#[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}