key_paths_macros/
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    // Error handling containers
9    Result,
10    Option,
11    Box,
12    Rc,
13    Arc,
14    Vec,
15    HashMap,
16    BTreeMap,
17    HashSet,
18    BTreeSet,
19    VecDeque,
20    LinkedList,
21    BinaryHeap,
22    // Synchronization primitives
23    Mutex,
24    RwLock,
25    // Reference counting with weak references
26    Weak,
27    // String types (currently unused)
28    // String,
29    // OsString,
30    // PathBuf,
31    // Nested container support
32    OptionBox,
33    OptionRc,
34    OptionArc,
35    BoxOption,
36    RcOption,
37    ArcOption,
38    VecOption,
39    OptionVec,
40    HashMapOption,
41    OptionHashMap,
42    // Arc with synchronization primitives
43    ArcMutex,
44    ArcRwLock,
45    // Tagged types
46    Tagged,
47}
48
49struct SomeStruct {
50    abc: String,
51}
52
53#[proc_macro_derive(Keypath)]
54pub fn derive_keypaths(input: TokenStream) -> TokenStream {
55    let ast = parse_macro_input!(input as DeriveInput);
56
57    // Get name
58    let name = &ast.ident;
59
60    // Get generics
61    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
62
63    // Process based on data type
64    let methods = match &ast.data {
65        Data::Struct(data) => {
66            match &data.fields {
67                Fields::Named(fields) => {
68                    let mut tokens = proc_macro2::TokenStream::new();
69                    for field in &fields.named {
70                        if let Some(field_name) = &field.ident {
71                            let field_type = &field.ty;
72                            let (kind, inner_ty) = extract_wrapper_inner_type(field_type);
73                            match (kind, inner_ty.clone()) {
74                                // Non-Options - simple one
75                                (WrapperKind::None, None) => {
76                                    tokens.extend(quote! {
77                                pub fn #field_name() -> key_paths_core::KeyPaths<#name, #field_type> {
78                                    key_paths_core::KeyPaths::readable(|s: &#name| &s.#field_name)
79                                }
80                            });
81                                }
82                                // Option types 
83                                (WrapperKind::Option, Some(inner_ty)) => {
84                                    tokens.extend(quote! {
85                                pub fn #field_name() -> key_paths_core::KeyPaths<#name, #inner_ty> {
86                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| s.#field_name.as_ref())
87                                }
88                            });
89                                }
90                                (WrapperKind::Result, Some(inner_ty)) => {
91                                    tokens.extend(quote! {
92                                pub fn #field_name() -> key_paths_core::KeyPaths<#name, #inner_ty> {
93                                    key_paths_core::KeyPaths::failable_readable(|s: &#name| s.#field_name.as_ref().ok())
94                                }
95                            });
96                                }
97
98                                _ => {}
99                            }
100                        }
101                    }
102
103                    tokens
104                }
105                Fields::Unnamed(fields) => {
106                    let mut tokens = proc_macro2::TokenStream::new();
107                    for (i, field) in fields.unnamed.iter().enumerate() {
108                        let field_type = &field.ty;
109                        // Process tuple field
110                    }
111                    tokens
112                }
113                Fields::Unit => {
114                    let mut tokens = proc_macro2::TokenStream::new();
115                    // Unit struct
116                    tokens
117                }
118            }
119        }
120        Data::Enum(data) => {
121            let mut tokens = proc_macro2::TokenStream::new();
122            for variant in &data.variants {
123                let variant_name = &variant.ident;
124                // Process variant
125            }
126            tokens
127        }
128        Data::Union(_) => {
129            let mut tokens = proc_macro2::TokenStream::new();
130            panic!("Unions not supported");
131            tokens
132        }
133    };
134
135    // // Generate code
136    // quote! {
137    //     impl #impl_generics MyTrait for #name #ty_generics #where_clause {
138    //         // Implementation
139    //         #methods
140    //     }
141    // }
142    // .into()
143
144    let expanded = quote! {
145        impl #name {
146            #methods
147        }
148    };
149
150    TokenStream::from(expanded)
151}
152
153fn extract_wrapper_inner_type(ty: &Type) -> (WrapperKind, Option<Type>) {
154    use syn::{GenericArgument, PathArguments};
155
156    if let Type::Path(tp) = ty {
157        if let Some(seg) = tp.path.segments.last() {
158            let ident_str = seg.ident.to_string();
159
160            if let PathArguments::AngleBracketed(ab) = &seg.arguments {
161                let args: Vec<_> = ab.args.iter().collect();
162
163                // Handle map types (HashMap, BTreeMap) - they have K, V parameters
164                if ident_str == "HashMap" || ident_str == "BTreeMap" {
165                    if let (Some(_key_arg), Some(value_arg)) = (args.get(0), args.get(1)) {
166                        if let GenericArgument::Type(inner) = value_arg {
167                            eprintln!("Detected {} type, extracting value type", ident_str);
168                            // Check for nested Option in map values
169                            let (inner_kind, inner_inner) = extract_wrapper_inner_type(inner);
170                            match (ident_str.as_str(), inner_kind) {
171                                ("HashMap", WrapperKind::Option) => {
172                                    return (WrapperKind::HashMapOption, inner_inner);
173                                }
174                                _ => {
175                                    return match ident_str.as_str() {
176                                        "HashMap" => (WrapperKind::HashMap, Some(inner.clone())),
177                                        "BTreeMap" => (WrapperKind::BTreeMap, Some(inner.clone())),
178                                        _ => (WrapperKind::None, None),
179                                    };
180                                }
181                            }
182                        }
183                    }
184                }
185                // Handle single-parameter container types
186                else if let Some(arg) = args.get(0) {
187                    if let GenericArgument::Type(inner) = arg {
188                        // Check for nested containers first
189                        let (inner_kind, inner_inner) = extract_wrapper_inner_type(inner);
190
191                        // Handle nested combinations
192                        match (ident_str.as_str(), inner_kind) {
193                            ("Option", WrapperKind::Box) => {
194                                return (WrapperKind::OptionBox, inner_inner);
195                            }
196                            ("Option", WrapperKind::Rc) => {
197                                return (WrapperKind::OptionRc, inner_inner);
198                            }
199                            ("Option", WrapperKind::Arc) => {
200                                return (WrapperKind::OptionArc, inner_inner);
201                            }
202                            ("Option", WrapperKind::Vec) => {
203                                return (WrapperKind::OptionVec, inner_inner);
204                            }
205                            ("Option", WrapperKind::HashMap) => {
206                                return (WrapperKind::OptionHashMap, inner_inner);
207                            }
208                            ("Box", WrapperKind::Option) => {
209                                return (WrapperKind::BoxOption, inner_inner);
210                            }
211                            ("Rc", WrapperKind::Option) => {
212                                return (WrapperKind::RcOption, inner_inner);
213                            }
214                            ("Arc", WrapperKind::Option) => {
215                                return (WrapperKind::ArcOption, inner_inner);
216                            }
217                            ("Vec", WrapperKind::Option) => {
218                                return (WrapperKind::VecOption, inner_inner);
219                            }
220                            ("HashMap", WrapperKind::Option) => {
221                                return (WrapperKind::HashMapOption, inner_inner);
222                            }
223                            ("Arc", WrapperKind::Mutex) => {
224                                return (WrapperKind::ArcMutex, inner_inner);
225                            }
226                            ("Arc", WrapperKind::RwLock) => {
227                                return (WrapperKind::ArcRwLock, inner_inner);
228                            }
229                            _ => {
230                                // Handle single-level containers
231                                return match ident_str.as_str() {
232                                    "Option" => (WrapperKind::Option, Some(inner.clone())),
233                                    "Box" => (WrapperKind::Box, Some(inner.clone())),
234                                    "Rc" => (WrapperKind::Rc, Some(inner.clone())),
235                                    "Arc" => (WrapperKind::Arc, Some(inner.clone())),
236                                    "Vec" => (WrapperKind::Vec, Some(inner.clone())),
237                                    "HashSet" => (WrapperKind::HashSet, Some(inner.clone())),
238                                    "BTreeSet" => (WrapperKind::BTreeSet, Some(inner.clone())),
239                                    "VecDeque" => (WrapperKind::VecDeque, Some(inner.clone())),
240                                    "LinkedList" => (WrapperKind::LinkedList, Some(inner.clone())),
241                                    "BinaryHeap" => (WrapperKind::BinaryHeap, Some(inner.clone())),
242                                    "Result" => (WrapperKind::Result, Some(inner.clone())),
243                                    "Mutex" => (WrapperKind::Mutex, Some(inner.clone())),
244                                    "RwLock" => (WrapperKind::RwLock, Some(inner.clone())),
245                                    "Weak" => (WrapperKind::Weak, Some(inner.clone())),
246                                    "Tagged" => (WrapperKind::Tagged, Some(inner.clone())),
247                                    _ => (WrapperKind::None, None),
248                                };
249                            }
250                        }
251                    }
252                }
253            }
254        }
255    }
256    (WrapperKind::None, None)
257}
258
259fn to_snake_case(name: &str) -> String {
260    let mut out = String::new();
261    for (i, c) in name.chars().enumerate() {
262        if c.is_uppercase() {
263            if i != 0 {
264                out.push('_');
265            }
266            out.push(c.to_ascii_lowercase());
267        } else {
268            out.push(c);
269        }
270    }
271    out
272}
273
274