key_paths_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{Data, DeriveInput, Fields, Type, parse_macro_input};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6enum WrapperKind {
7    None,
8    Option,
9    Box,
10    Rc,
11    Arc,
12}
13
14#[proc_macro_derive(Keypaths)]
15pub fn derive_keypaths(input: TokenStream) -> TokenStream {
16    let input = parse_macro_input!(input as DeriveInput);
17    let name = input.ident;
18
19    let methods = match input.data {
20        Data::Struct(data_struct) => match data_struct.fields {
21            Fields::Named(fields_named) => {
22                let mut tokens = proc_macro2::TokenStream::new();
23                for field in fields_named.named.iter() {
24                    let field_ident = field.ident.as_ref().unwrap();
25                    let ty = &field.ty;
26
27                    let r_fn = format_ident!("{}_r", field_ident);
28                    let w_fn = format_ident!("{}_w", field_ident);
29                    let fr_fn = format_ident!("{}_fr", field_ident);
30                    let fw_fn = format_ident!("{}_fw", field_ident);
31
32                    let (kind, inner_ty) = extract_wrapper_inner_type(ty);
33
34                    match (kind, inner_ty) {
35                        (WrapperKind::Option, Some(inner_ty)) => {
36                            // Option<T>
37                            tokens.extend(quote! {
38                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #ty> {
39                                    key_paths_core::KeyPaths::readable(|s: &#name| &s.#field_ident)
40                                }
41                                pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #ty> {
42                                    key_paths_core::KeyPaths::writable(|s: &mut #name| &mut s.#field_ident)
43                                }
44                                pub fn #fr_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
45                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| s.#field_ident.as_ref())
46                                }
47                                pub fn #fw_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
48                                    key_paths_core::KeyPaths::failable_writable(|s: &mut #name| s.#field_ident.as_mut())
49                                }
50                            });
51                        }
52                        (WrapperKind::Box, Some(inner_ty)) => {
53                            // Box<T>
54                            tokens.extend(quote! {
55                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
56                                    key_paths_core::KeyPaths::readable(|s: &#name| &*s.#field_ident)
57                                }
58                                pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
59                                    key_paths_core::KeyPaths::writable(|s: &mut #name| &mut *s.#field_ident)
60                                }
61                                pub fn #fr_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
62                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| Some(&*s.#field_ident))
63                                }
64                                pub fn #fw_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
65                                    key_paths_core::KeyPaths::failable_writable(|s: &mut #name| Some(&mut *s.#field_ident))
66                                }
67                            });
68                        }
69                        (WrapperKind::Rc, Some(inner_ty)) | (WrapperKind::Arc, Some(inner_ty)) => {
70                            // Rc<T> or Arc<T> -> read-only only
71                            tokens.extend(quote! {
72                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
73                                    key_paths_core::KeyPaths::readable(|s: &#name| &*s.#field_ident)
74                                }
75                                pub fn #fr_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
76                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| Some(&*s.#field_ident))
77                                }
78                            });
79                        }
80                        (WrapperKind::None, None) => {
81                            // Regular field
82                            tokens.extend(quote! {
83                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #ty> {
84                                    key_paths_core::KeyPaths::readable(|s: &#name| &s.#field_ident)
85                                }
86                                pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #ty> {
87                                    key_paths_core::KeyPaths::writable(|s: &mut #name| &mut s.#field_ident)
88                                }
89                                pub fn #fr_fn() -> key_paths_core::KeyPaths<#name, #ty> {
90                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| Some(&s.#field_ident))
91                                }
92                                pub fn #fw_fn() -> key_paths_core::KeyPaths<#name, #ty> {
93                                    key_paths_core::KeyPaths::failable_writable(|s: &mut #name| Some(&mut s.#field_ident))
94                                }
95                            });
96                        }
97                        // Shouldn't get here: fallback to regular field behavior
98                        _ => {
99                            tokens.extend(quote! {
100                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #ty> {
101                                    key_paths_core::KeyPaths::readable(|s: &#name| &s.#field_ident)
102                                }
103                                pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #ty> {
104                                    key_paths_core::KeyPaths::writable(|s: &mut #name| &mut s.#field_ident)
105                                }
106                            });
107                        }
108                    }
109                }
110                tokens
111            }
112            Fields::Unnamed(unnamed) => {
113                // tuple struct fields (0,1,2...)
114                let mut tokens = proc_macro2::TokenStream::new();
115                for (idx, field) in unnamed.unnamed.iter().enumerate() {
116                    let idx_lit = syn::Index::from(idx);
117                    let ty = &field.ty;
118
119                    let r_fn = format_ident!("f{}_r", idx);
120                    let w_fn = format_ident!("f{}_w", idx);
121                    let fr_fn = format_ident!("f{}_fr", idx);
122                    let fw_fn = format_ident!("f{}_fw", idx);
123
124                    let (kind, inner_ty) = extract_wrapper_inner_type(ty);
125
126                    match (kind, inner_ty) {
127                        (WrapperKind::Option, Some(inner_ty)) => {
128                            tokens.extend(quote! {
129                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #ty> {
130                                    key_paths_core::KeyPaths::readable(|s: &#name| &s.#idx_lit)
131                                }
132                                pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #ty> {
133                                    key_paths_core::KeyPaths::writable(|s: &mut #name| &mut s.#idx_lit)
134                                }
135                                pub fn #fr_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
136                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| s.#idx_lit.as_ref())
137                                }
138                                pub fn #fw_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
139                                    key_paths_core::KeyPaths::failable_writable(|s: &mut #name| s.#idx_lit.as_mut())
140                                }
141                            });
142                        }
143                        (WrapperKind::Box, Some(inner_ty)) => {
144                            tokens.extend(quote! {
145                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
146                                    key_paths_core::KeyPaths::readable(|s: &#name| &*s.#idx_lit)
147                                }
148                                pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
149                                    key_paths_core::KeyPaths::writable(|s: &mut #name| &mut *s.#idx_lit)
150                                }
151                                pub fn #fr_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
152                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| Some(&*s.#idx_lit))
153                                }
154                                pub fn #fw_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
155                                    key_paths_core::KeyPaths::failable_writable(|s: &mut #name| Some(&mut *s.#idx_lit))
156                                }
157                            });
158                        }
159                        (WrapperKind::Rc, Some(inner_ty)) | (WrapperKind::Arc, Some(inner_ty)) => {
160                            tokens.extend(quote! {
161                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
162                                    key_paths_core::KeyPaths::readable(|s: &#name| &*s.#idx_lit)
163                                }
164                                pub fn #fr_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
165                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| Some(&*s.#idx_lit))
166                                }
167                            });
168                        }
169                        (WrapperKind::None, None) => {
170                            tokens.extend(quote! {
171                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #ty> {
172                                    key_paths_core::KeyPaths::readable(|s: &#name| &s.#idx_lit)
173                                }
174                                pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #ty> {
175                                    key_paths_core::KeyPaths::writable(|s: &mut #name| &mut s.#idx_lit)
176                                }
177                                pub fn #fr_fn() -> key_paths_core::KeyPaths<#name, #ty> {
178                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| Some(&s.#idx_lit))
179                                }
180                                pub fn #fw_fn() -> key_paths_core::KeyPaths<#name, #ty> {
181                                    key_paths_core::KeyPaths::failable_writable(|s: &mut #name| Some(&mut s.#idx_lit))
182                                }
183                            });
184                        }
185                        _ => {
186                            // fallback
187                            tokens.extend(quote! {
188                                pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #ty> {
189                                    key_paths_core::KeyPaths::readable(|s: &#name| &s.#idx_lit)
190                                }
191                            });
192                        }
193                    }
194                }
195                tokens
196            }
197            _ => quote! {
198                compile_error!("Keypaths derive supports only structs with named or unnamed fields");
199            },
200        },
201        Data::Enum(data_enum) => {
202            // Support enum unit variants and single-field tuple variants.
203            let mut tokens = proc_macro2::TokenStream::new();
204            for variant in data_enum.variants.iter() {
205                let v_ident = &variant.ident;
206                let snake = format_ident!("{}", to_snake_case(&v_ident.to_string()));
207                let r_fn = format_ident!("{}_case_r", snake);
208                let w_fn = format_ident!("{}_case_w", snake);
209
210                match &variant.fields {
211                    Fields::Unit => {
212                        tokens.extend(quote! {
213                            pub fn #r_fn() -> key_paths_core::KeyPaths<#name, ()> {
214                                static UNIT: () = ();
215                                key_paths_core::KeyPaths::readable_enum(
216                                    |_| #name::#v_ident,
217                                    |e: &#name| match e { #name::#v_ident => Some(&UNIT), _ => None }
218                                )
219                            }
220                        });
221                    }
222                    Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
223                        let field_ty = &unnamed.unnamed.first().unwrap().ty;
224                        let (kind, inner_ty_opt) = extract_wrapper_inner_type(field_ty);
225
226                        match (kind, inner_ty_opt) {
227                            (WrapperKind::Option, Some(inner_ty)) => {
228                                tokens.extend(quote! {
229                                    pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
230                                        key_paths_core::KeyPaths::readable_enum(
231                                            #name::#v_ident,
232                                            |e: &#name| match e { #name::#v_ident(v) => v.as_ref(), _ => None }
233                                        )
234                                    }
235                                    pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
236                                        key_paths_core::KeyPaths::writable_enum(
237                                            #name::#v_ident,
238                                            |e: &#name| match e { #name::#v_ident(v) => v.as_ref(), _ => None },
239                                            |e: &mut #name| match e { #name::#v_ident(v) => v.as_mut(), _ => None },
240                                        )
241                                    }
242                                });
243                            }
244                            (WrapperKind::Box, Some(inner_ty)) => {
245                                tokens.extend(quote! {
246                                    pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
247                                        key_paths_core::KeyPaths::readable_enum(
248                                            #name::#v_ident,
249                                            |e: &#name| match e { #name::#v_ident(v) => Some(&*v), _ => None }
250                                        )
251                                    }
252                                    pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
253                                        key_paths_core::KeyPaths::writable_enum(
254                                            #name::#v_ident,
255                                            |e: &#name| match e { #name::#v_ident(v) => Some(&*v), _ => None },
256                                            |e: &mut #name| match e { #name::#v_ident(v) => Some(&mut *v), _ => None },
257                                        )
258                                    }
259                                });
260                            }
261                            (WrapperKind::Rc, Some(inner_ty)) | (WrapperKind::Arc, Some(inner_ty)) => {
262                                tokens.extend(quote! {
263                                    pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
264                                        key_paths_core::KeyPaths::readable_enum(
265                                            #name::#v_ident,
266                                            |e: &#name| match e { #name::#v_ident(v) => Some(&*v), _ => None }
267                                        )
268                                    }
269                                });
270                            }
271                            (WrapperKind::None, None) => {
272                                // plain payload
273                                let inner_ty = field_ty;
274                                tokens.extend(quote! {
275                                    pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
276                                        key_paths_core::KeyPaths::readable_enum(
277                                            #name::#v_ident,
278                                            |e: &#name| match e { #name::#v_ident(v) => Some(v), _ => None }
279                                        )
280                                    }
281                                    pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
282                                        key_paths_core::KeyPaths::writable_enum(
283                                            #name::#v_ident,
284                                            |e: &#name| match e { #name::#v_ident(v) => Some(v), _ => None },
285                                            |e: &mut #name| match e { #name::#v_ident(v) => Some(v), _ => None },
286                                        )
287                                    }
288                                });
289                            }
290                            _ => {
291                                // Fallback: expose the raw field as readable
292                                tokens.extend(quote! {
293                                    pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #field_ty> {
294                                        key_paths_core::KeyPaths::readable_enum(
295                                            #name::#v_ident,
296                                            |e: &#name| match e { #name::#v_ident(v) => Some(v), _ => None }
297                                        )
298                                    }
299                                });
300                            }
301                        }
302                    }
303                    _ => {
304                        tokens.extend(quote! {
305                            compile_error!("Casepaths derive supports only unit and single-field tuple variants");
306                        });
307                    }
308                }
309            }
310            tokens
311        }
312        _ => quote! {
313            compile_error!("Keypaths derive supports only structs and enums");
314        },
315    };
316
317    let expanded = quote! {
318        impl #name {
319            #methods
320        }
321    };
322
323    TokenStream::from(expanded)
324}
325
326fn extract_wrapper_inner_type(ty: &Type) -> (WrapperKind, Option<Type>) {
327    use syn::{GenericArgument, PathArguments};
328    if let Type::Path(tp) = ty {
329        if let Some(seg) = tp.path.segments.last() {
330            let ident_str = seg.ident.to_string();
331            if let PathArguments::AngleBracketed(ab) = &seg.arguments {
332                for arg in ab.args.iter() {
333                    if let GenericArgument::Type(inner) = arg {
334                        return match ident_str.as_str() {
335                            "Option" => (WrapperKind::Option, Some(inner.clone())),
336                            "Box"    => (WrapperKind::Box, Some(inner.clone())),
337                            "Rc"     => (WrapperKind::Rc, Some(inner.clone())),
338                            "Arc"    => (WrapperKind::Arc, Some(inner.clone())),
339                            _        => (WrapperKind::None, None),
340                        };
341                    }
342                }
343            }
344        }
345    }
346    (WrapperKind::None, None)
347}
348
349fn to_snake_case(name: &str) -> String {
350    let mut out = String::new();
351    for (i, c) in name.chars().enumerate() {
352        if c.is_uppercase() {
353            if i != 0 {
354                out.push('_');
355            }
356            out.push(c.to_ascii_lowercase());
357        } else {
358            out.push(c);
359        }
360    }
361    out
362}
363
364// #[proc_macro_derive(Casepaths)]
365// pub fn derive_casepaths(input: TokenStream) -> TokenStream {
366//     // NOTE: the Casepaths derive was already handled inside the Keypaths impl above.
367//     // If you prefer a separate derive for Casepaths (kept for backward compat), simply call the same
368//     // generation logic or extract shared helpers. For brevity, this stub keeps the behavior minimal.
369//     let input = parse_macro_input!(input as DeriveInput);
370//     let name = input.ident;
371
372//     // Delegate to the same enum handling as Keypaths for single-field tuple / unit variants.
373//     // (You can copy-paste the enum branch above into a shared helper if you want real dedup.)
374//     let tokens = match input.data {
375//         Data::Enum(_) => {
376//             quote! {
377//                 // Casepaths derive is intentionally left as a no-op here because Keypaths
378//                 // already implements the enum helpers. If you want separate Casepaths
379//                 // behavior, implement it similarly to the enum branch above.
380//             }
381//         }
382//         _ => quote! { compile_error!("Casepaths can only be derived for enums"); },
383//     };
384
385//     let expanded = quote! {
386//         impl #name {
387//             #tokens
388//         }
389//     };
390
391//     TokenStream::from(expanded)
392// }
393
394
395#[proc_macro_derive(Casepaths)]
396pub fn derive_casepaths(input: TokenStream) -> TokenStream {
397    let input = parse_macro_input!(input as DeriveInput);
398    let name = input.ident;
399
400    let tokens = match input.data {
401        Data::Enum(data_enum) => {
402            let mut tokens = proc_macro2::TokenStream::new();
403            for variant in data_enum.variants.iter() {
404                let v_ident = &variant.ident;
405                let snake = format_ident!("{}", to_snake_case(&v_ident.to_string()));
406                let r_fn = format_ident!("{}_case_r", snake);
407                let w_fn = format_ident!("{}_case_w", snake);
408
409                match &variant.fields {
410                    Fields::Unit => {
411                        tokens.extend(quote! {
412                            pub fn #r_fn() -> key_paths_core::KeyPaths<#name, ()> {
413                                static UNIT: () = ();
414                                key_paths_core::KeyPaths::readable_enum(
415                                    |_| #name::#v_ident,
416                                    |e: &#name| match e { #name::#v_ident => Some(&UNIT), _ => None }
417                                )
418                            }
419                        });
420                    }
421                    Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
422                        let inner_ty = &unnamed.unnamed.first().unwrap().ty;
423                        tokens.extend(quote! {
424                            pub fn #r_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
425                                key_paths_core::KeyPaths::readable_enum(
426                                    #name::#v_ident,
427                                    |e: &#name| match e { #name::#v_ident(v) => Some(v), _ => None }
428                                )
429                            }
430                            pub fn #w_fn() -> key_paths_core::KeyPaths<#name, #inner_ty> {
431                                key_paths_core::KeyPaths::writable_enum(
432                                    #name::#v_ident,
433                                    |e: &#name| match e { #name::#v_ident(v) => Some(v), _ => None },
434                                    |e: &mut #name| match e { #name::#v_ident(v) => Some(v), _ => None },
435                                )
436                            }
437                        });
438                    }
439                    _ => {
440                        tokens.extend(quote! {
441                            compile_error!("Casepaths derive supports only unit and single-field tuple variants");
442                        });
443                    }
444                }
445            }
446            tokens
447        }
448        _ => quote! { compile_error!("Casepaths can only be derived for enums"); },
449    };
450
451    let expanded = quote! {
452        impl #name {
453            #tokens
454        }
455    };
456
457    TokenStream::from(expanded)
458}