1extern crate proc_macro;
47use proc_macro::TokenStream;
48use quote::{format_ident, quote};
49use syn::{parse_macro_input, Data, DeriveInput, Fields, Ident};
50
51fn expand(input: DeriveInput) -> proc_macro2::TokenStream {
52 let ty_ident = input.ident;
53 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
54
55 match input.data {
56 Data::Struct(ref s) => match s.fields {
57 Fields::Named(ref fields_named) => {
58 let acc_fns = fields_named.named.iter().map(|f| {
59 let fname: &Ident = f.ident.as_ref().unwrap();
60 let acc_fn = format_ident!("acc_{}", fname);
61 let fty = &f.ty;
62 quote! {
63 pub const fn #acc_fn() -> pathmod::Accessor<#ty_ident #ty_generics, #fty> {
65 let off = core::mem::offset_of!(#ty_ident #ty_generics, #fname) as isize;
66 unsafe { pathmod::Accessor::<#ty_ident #ty_generics, #fty>::from_offset(off) }
68 }
69 }
70 });
71
72 let with_fns = fields_named.named.iter().map(|f| {
73 let fname: &Ident = f.ident.as_ref().unwrap();
74 let with_fn = format_ident!("with_{}", fname);
75 let fty = &f.ty;
76 quote! {
77 pub fn #with_fn(mut self, new_val: #fty) -> Self {
83 self.#fname = new_val;
84 self
85 }
86 }
87 });
88
89 quote! {
90 impl #impl_generics #ty_ident #ty_generics #where_clause {
91 #(#acc_fns)*
92 #(#with_fns)*
93 }
94 }
95 }
96 Fields::Unnamed(ref fields_unnamed) => {
97 let acc_fns = fields_unnamed.unnamed.iter().enumerate().map(|(i, f)| {
98 let acc_fn = format_ident!("acc_{}", i);
99 let fty = &f.ty;
100 let index = syn::Index::from(i);
101 quote! {
102 pub const fn #acc_fn() -> pathmod::Accessor<#ty_ident #ty_generics, #fty> {
104 let off = core::mem::offset_of!(#ty_ident #ty_generics, #index) as isize;
105 unsafe { pathmod::Accessor::<#ty_ident #ty_generics, #fty>::from_offset(off) }
107 }
108 }
109 });
110 let with_fns = fields_unnamed.unnamed.iter().enumerate().map(|(i, f)| {
111 let with_fn = format_ident!("with_{}", i);
112 let fty = &f.ty;
113 let index = syn::Index::from(i);
114 quote! {
115 pub fn #with_fn(mut self, new_val: #fty) -> Self {
119 self.#index = new_val;
120 self
121 }
122 }
123 });
124 quote! {
125 impl #impl_generics #ty_ident #ty_generics #where_clause {
126 #(#acc_fns)*
127 #(#with_fns)*
128 }
129 }
130 }
131 Fields::Unit => {
132 let msg = "#[derive(Accessor)] does not support unit structs";
133 quote! { compile_error!(#msg); }
134 }
135 },
136 _ => {
137 let msg = "#[derive(Accessor)] can only be used on structs";
138 quote! { compile_error!(#msg); }
139 }
140 }
141}
142
143#[proc_macro_derive(Accessor)]
151pub fn accessor_derive(input: TokenStream) -> TokenStream {
152 let input: DeriveInput = parse_macro_input!(input as DeriveInput);
153 let ts = expand(input);
154 TokenStream::from(ts)
155}
156
157fn expand_enum(input: DeriveInput) -> proc_macro2::TokenStream {
158 let ty_ident = input.ident;
160 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
161
162 match input.data {
163 Data::Enum(en) => {
164 let mut per_variant_tokens = Vec::new();
166 let mut error_msg: Option<&'static str> = None;
167 for v in en.variants.iter() {
168 let v_ident = &v.ident;
169 match &v.fields {
170 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
171 let fty = &fields.unnamed.first().unwrap().ty;
172 let is_fn = format_ident!("is_{}", v_ident.to_string().to_lowercase());
173 let as_fn = format_ident!("as_{}", v_ident.to_string().to_lowercase());
174 let as_mut_fn =
175 format_ident!("as_{}_mut", v_ident.to_string().to_lowercase());
176 let set_fn = format_ident!("set_{}", v_ident.to_string().to_lowercase());
177 let map_fn = format_ident!("map_{}", v_ident.to_string().to_lowercase());
178 per_variant_tokens.push(quote! {
179 #[inline]
180 pub fn #is_fn(&self) -> bool { matches!(self, Self::#v_ident(_)) }
181 #[inline]
182 pub fn #as_fn(&self) -> Option<& #fty> { if let Self::#v_ident(ref v) = self { Some(v) } else { None } }
183 #[inline]
184 pub fn #as_mut_fn(&mut self) -> Option<&mut #fty> { if let Self::#v_ident(ref mut v) = self { Some(v) } else { None } }
185 #[inline]
186 pub fn #set_fn(&mut self, val: #fty) { *self = Self::#v_ident(val); }
187 #[inline]
188 pub fn #map_fn(&mut self, f: impl FnOnce(&mut #fty)) { if let Self::#v_ident(ref mut v) = self { f(v); } }
189 });
190 }
191 Fields::Named(fields) if fields.named.len() == 1 => {
192 let _ = &fields; error_msg = Some("#[derive(EnumAccess)] currently supports only tuple variants with exactly one field; named-field single variants are not yet supported");
194 break;
195 }
196 Fields::Unit => {
197 error_msg = Some(
198 "#[derive(EnumAccess)] does not support unit variants in this MVP",
199 );
200 break;
201 }
202 _ => {
203 error_msg = Some("#[derive(EnumAccess)] supports only tuple variants with exactly one field");
204 break;
205 }
206 }
207 }
208 if let Some(msg) = error_msg {
209 return quote! { compile_error!(#msg); };
210 }
211 quote! {
212 impl #impl_generics #ty_ident #ty_generics #where_clause {
213 #(#per_variant_tokens)*
214 }
215 }
216 }
217 _ => {
218 quote! { compile_error!("#[derive(EnumAccess)] can only be used on enums"); }
219 }
220 }
221}
222
223#[proc_macro_derive(EnumAccess)]
228pub fn enum_access_derive(input: TokenStream) -> TokenStream {
229 let input: DeriveInput = parse_macro_input!(input as DeriveInput);
230 let ts = expand_enum(input);
231 TokenStream::from(ts)
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use syn::parse_quote;
238
239 #[test]
240 fn expands_named_struct() {
241 let di: DeriveInput = parse_quote! {
242 struct S { a: i32, b: i64 }
243 };
244 let out = expand(di);
245 let s = out.to_string();
246 assert!(s.contains("acc_a"));
247 assert!(s.contains("acc_b"));
248 }
249
250 #[test]
251 fn expands_tuple_struct() {
252 let di: DeriveInput = parse_quote! {
253 struct P(i32, i64);
254 };
255 let out = expand(di);
256 let s = out.to_string();
257 assert!(s.contains("acc_0"));
258 assert!(s.contains("acc_1"));
259 }
260
261 #[test]
262 fn errors_on_unit_struct() {
263 let di: DeriveInput = parse_quote! { struct U; };
264 let out = expand(di);
265 let s = out.to_string();
266 assert!(s.contains("compile_error") && s.contains("does not support unit structs"));
267 }
268
269 #[test]
270 fn errors_on_enum() {
271 let di: DeriveInput = parse_quote! { enum E { A } };
272 let out = expand(di);
273 let s = out.to_string();
274 assert!(s.contains("compile_error") && s.contains("only be used on structs"));
275 }
276
277 #[test]
279 fn enum_access_positive_single_field_tuple_variant() {
280 let di: DeriveInput = parse_quote! { enum Msg { Int(i32), Text(String) } };
281 let out = expand_enum(di);
282 let s = out.to_string();
283 assert!(s.contains("is_int"));
285 assert!(s.contains("as_int"));
286 assert!(s.contains("as_int_mut"));
287 assert!(s.contains("set_int"));
288 assert!(s.contains("map_int"));
289 assert!(s.contains("is_text"));
290 assert!(s.contains("as_text"));
291 assert!(s.contains("as_text_mut"));
292 assert!(s.contains("set_text"));
293 assert!(s.contains("map_text"));
294 }
295
296 #[test]
297 fn enum_access_error_on_unit_variant() {
298 let di: DeriveInput = parse_quote! { enum E { A } };
299 let out = expand_enum(di);
300 let s = out.to_string();
301 assert!(s.contains("compile_error") && s.contains("does not support unit variants"));
302 }
303
304 #[test]
305 fn enum_access_error_on_multi_field_variant() {
306 let di: DeriveInput = parse_quote! { enum E { Both(i32, i32) } };
307 let out = expand_enum(di);
308 let s = out.to_string();
309 assert!(
310 s.contains("compile_error")
311 && s.contains("supports only tuple variants with exactly one field")
312 );
313 }
314
315 #[test]
316 fn enum_access_error_on_named_single_field_variant() {
317 let di: DeriveInput = parse_quote! { enum E { V { v: i32 } } };
318 let out = expand_enum(di);
319 let s = out.to_string();
320 assert!(
321 s.contains("compile_error") && s.contains("currently supports only tuple variants")
322 );
323 }
324
325 #[test]
326 fn enum_access_error_on_non_enum() {
327 let di: DeriveInput = parse_quote! { struct NotEnum { a: i32 } };
328 let out = expand_enum(di);
329 let s = out.to_string();
330 assert!(s.contains("compile_error") && s.contains("can only be used on enums"));
331 }
332
333 #[test]
335 fn expands_generics_with_where_clause() {
336 let di: DeriveInput = parse_quote! {
337 struct Wrap<T: Clone, U>
338 where
339 U: core::fmt::Debug,
340 {
341 t: T,
342 u: U,
343 }
344 };
345 let out = expand(di);
346 let s = out.to_string();
347 assert!(s.contains("acc_t"));
349 assert!(s.contains("acc_u"));
350 assert!(s.contains("Debug") || s.contains("where"));
352 }
353
354 #[test]
356 fn enum_access_empty_enum_generates_impl() {
357 let di: DeriveInput = parse_quote! { enum Z {} };
358 let out = expand_enum(di);
359 let s = out.to_string();
360 assert!(s.contains("impl") && s.contains("Z"));
362 }
363}