Skip to main content

spenso_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{ToTokens, format_ident, quote}; // Ensure ToTokens is imported
4use syn::{
5    Attribute,
6    Data,
7    DeriveInput,
8    Expr,
9    ExprLit,
10    ExprPath,
11    Ident,
12    Lit,
13    LitStr,
14    Meta,
15    Path, // Use Path and Meta
16    Token,
17    parse::Parser,
18    parse_macro_input,
19    punctuated::Punctuated,
20    spanned::Spanned,
21};
22
23// --- RepresentationAttrs struct and parse_representation_attributes function (keep as is) ---
24#[derive(Debug)]
25struct RepresentationAttrs {
26    name: LitStr,
27    is_self_dual: bool,
28    custom_dual_name: Option<Ident>,
29}
30
31fn parse_representation_attributes(attrs: &[Attribute]) -> Result<RepresentationAttrs, syn::Error> {
32    // ... (implementation is correct) ...
33    let mut rep_name: Option<LitStr> = None;
34    let mut is_self_dual = false;
35    let mut custom_dual_name: Option<Ident> = None;
36
37    let rep_attr = attrs
38        .iter()
39        .find(|attr| attr.path().is_ident("representation"))
40        .ok_or_else(|| {
41            syn::Error::new(
42                Span::call_site(), // Consider spanning the struct ident if possible
43                "Missing #[representation(...)] attribute",
44            )
45        })?;
46
47    let meta = &rep_attr.meta;
48    let list = match meta {
49        Meta::List(list) => list,
50        _ => {
51            return Err(syn::Error::new_spanned(
52                meta,
53                "Expected #[representation(...)] format",
54            ));
55        }
56    };
57
58    let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
59    let nested_metas = parser.parse2(list.tokens.clone()).map_err(|e| {
60        syn::Error::new(
61            e.span(),
62            format!("Failed to parse attribute arguments: {}", e),
63        )
64    })?;
65
66    for meta_item in nested_metas.iter() {
67        match meta_item {
68            Meta::NameValue(nv) if nv.path.is_ident("name") => {
69                if rep_name.is_some() {
70                    return Err(syn::Error::new_spanned(nv, "Duplicate `name` specified"));
71                }
72                if let Expr::Lit(ExprLit {
73                    lit: Lit::Str(lit_str),
74                    ..
75                }) = &nv.value
76                {
77                    rep_name = Some(lit_str.clone());
78                } else {
79                    return Err(syn::Error::new_spanned(
80                        &nv.value,
81                        "Expected string literal for `name`",
82                    ));
83                }
84            }
85            Meta::Path(path) if path.is_ident("self_dual") => {
86                if is_self_dual {
87                    return Err(syn::Error::new_spanned(
88                        path,
89                        "Duplicate `self_dual` specified",
90                    ));
91                }
92                is_self_dual = true;
93            }
94            Meta::NameValue(nv) if nv.path.is_ident("dual_name") => {
95                if custom_dual_name.is_some() {
96                    return Err(syn::Error::new_spanned(
97                        nv,
98                        "Duplicate `dual_name` specified",
99                    ));
100                }
101                match &nv.value {
102                    Expr::Lit(ExprLit {
103                        lit: Lit::Str(lit_str),
104                        ..
105                    }) => {
106                        custom_dual_name = Some(Ident::new(&lit_str.value(), lit_str.span()));
107                    }
108                    Expr::Path(ExprPath { path, .. }) => {
109                        if let Some(ident) = path.get_ident() {
110                            custom_dual_name = Some(ident.clone());
111                        } else {
112                            return Err(syn::Error::new_spanned(
113                                &nv.value,
114                                "Expected simple identifier for `dual_name` (e.g., MyDualName)",
115                            ));
116                        }
117                    }
118                    _ => {
119                        return Err(syn::Error::new_spanned(
120                            &nv.value,
121                            "Expected string literal or identifier for `dual_name`",
122                        ));
123                    }
124                }
125            }
126            _ => {
127                return Err(syn::Error::new_spanned(
128                    meta_item,
129                    "Unsupported item in #[representation(...)] attribute",
130                ));
131            }
132        }
133    }
134
135    let name = rep_name.ok_or_else(|| {
136        syn::Error::new_spanned(
137            list.tokens.clone(),
138            "Missing required `name = \"...\"` in #[representation(...)]",
139        )
140    })?;
141
142    if is_self_dual && custom_dual_name.is_some() {
143        let error_span = nested_metas
144            .iter()
145            .find(|m| matches!(m, Meta::NameValue(nv) if nv.path.is_ident("dual_name")))
146            .map_or_else(|| list.tokens.span(), |m| m.span());
147
148        return Err(syn::Error::new(
149            error_span,
150            "`dual_name` cannot be specified for a `self_dual` representation",
151        ));
152    }
153
154    Ok(RepresentationAttrs {
155        name,
156        is_self_dual,
157        custom_dual_name,
158    })
159}
160
161// --- Revised get_filtered_derive_paths using Meta parsing ---
162fn get_filtered_derive_paths(attrs: &[Attribute]) -> Result<Vec<Path>, syn::Error> {
163    let mut derived_traits = Vec::new();
164
165    for attr in attrs {
166        // Ensure it's the derive attribute: #[derive(...)]
167        if attr.path().is_ident("derive") {
168            // Use the Meta parser for the arguments inside derive(...)
169            match attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated) {
170                Ok(nested_metas) => {
171                    // eprintln!("    Parsed derive args as Metas successfully: {:#?}", nested_metas.iter().map(|m| m.to_token_stream().to_string()).collect::<Vec<_>>());
172                    for meta in nested_metas {
173                        // Standard derives like Debug, Clone, Serialize should appear as Meta::Path
174                        if let Meta::Path(path) = meta {
175                            // Check if the last segment of the path is "SimpleRepresentation"
176                            let is_target_derive = path
177                                .segments
178                                .last()
179                                .is_some_and(|segment| segment.ident == "SimpleRepresentation");
180
181                            if !is_target_derive {
182                                derived_traits.push(path); // Keep the original Path struct
183                            }
184                        } else {
185                            // If we find something else (like Meta::List or Meta::NameValue)
186                            // in a derive list, it's unexpected for standard traits.
187                            // We could ignore it or error. Let's error for clarity.
188                            return Err(syn::Error::new_spanned(
189                                meta, // Span the problematic meta item
190                                "Expected simple trait paths (e.g., Debug, Clone) in derive attribute, found other meta item.",
191                            ));
192                        }
193                    }
194                }
195                Err(e) => {
196                    // If parsing as Metas fails, the derive attribute format is likely malformed.
197                    // Return the error, pointing at the problematic attribute
198                    return Err(syn::Error::new_spanned(
199                        attr.to_token_stream(), // Span the whole attribute
200                        format!(
201                            "Failed to parse derive arguments: {}. Check syntax inside #[derive(...)].",
202                            e
203                        ),
204                    ));
205                }
206            }
207        }
208    }
209
210    Ok(derived_traits)
211}
212
213#[proc_macro_derive(SimpleRepresentation, attributes(representation))]
214pub fn derive_simple_representation(input: TokenStream) -> TokenStream {
215    let input = parse_macro_input!(input as DeriveInput);
216
217    // --- Extract Struct Info ---
218    let fields = match &input.data {
219        Data::Struct(s) => s.fields.clone(),
220        _ => {
221            return syn::Error::new_spanned(
222                &input.ident,
223                "SimpleRepresentation can only be derived for structs",
224            )
225            .to_compile_error()
226            .into();
227        }
228    };
229
230    // --- Parse Attributes ---
231    let vis = &input.vis;
232    let repr_attrs = match parse_representation_attributes(&input.attrs) {
233        Ok(attrs) => attrs,
234        Err(e) => return e.to_compile_error().into(),
235    };
236    // Use the revised helper function
237    let derived_traits = match get_filtered_derive_paths(&input.attrs) {
238        Ok(traits) => traits,
239        Err(e) => return e.to_compile_error().into(),
240    };
241
242    let base_type_ident = &input.ident;
243    let name_lit = &repr_attrs.name;
244    let is_self_dual = repr_attrs.is_self_dual;
245
246    // --- Bounds needed for RepName impls ---
247    let base_bounds = quote! { Default + Copy };
248    let dual_bounds = quote! { Default + Copy };
249
250    // --- Common RepName Implementation Parts ---
251    let base_repname_common_impl = quote! {
252        #[inline]
253        fn from_library_rep(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, ::spenso::structure::representation::RepresentationError>{
254            rep.try_into()
255        }
256        #[inline] fn base(&self) -> Self::Base where Self::Base: Default { Self::Base::default() }
257        #[inline] fn is_base(&self) -> bool { ::std::any::TypeId::of::<Self>() == ::std::any::TypeId::of::<Self::Base>() }
258    };
259
260    // --- Display Impls ---
261    let base_display_impl = quote! {
262        impl ::std::fmt::Display for #base_type_ident where #base_type_ident: Copy + Into<::spenso::structure::representation::LibraryRep> {
263            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { write!(f, "{}", ::spenso::structure::representation::LibraryRep::from(*self)) }
264        }
265    };
266
267    // --- Branch based on duality ---
268    let expanded = if is_self_dual {
269        // ===== Self-Dual Case ===== (Remains Correct)
270        let rep_new_call =
271            quote! { ::spenso::structure::representation::LibraryRep::new_self_dual(#name_lit) };
272        let base_from_impl = quote! {
273            impl From<#base_type_ident> for ::spenso::structure::representation::LibraryRep
274                where #base_type_ident: Copy
275                {
276                    fn from(_value: #base_type_ident) -> Self {
277                        #rep_new_call.expect(concat!("Failed to create self-dual Rep for ", #name_lit))
278                    }
279                }
280        };
281
282        let base_try_from_impl = quote! {
283            impl TryFrom<::spenso::structure::representation::LibraryRep> for #base_type_ident where #base_type_ident: Default {
284                type Error = ::spenso::structure::representation::RepresentationError;
285
286                fn try_from(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, Self::Error>   {
287                    let expected_rep = #rep_new_call.expect(concat!("Failed to create self-dual Rep for ", #name_lit));
288                    if rep == expected_rep {
289                        ::std::result::Result::Ok(#base_type_ident::default())
290                    } else {
291                        ::std::result::Result::Err(::spenso::structure::representation::RepresentationError::WrongRepresentationError(#name_lit.to_owned(), rep.to_string()))
292                    }
293                }
294            }
295        };
296
297        let base_repname_impl = quote! {
298            impl ::spenso::structure::representation::RepName for #base_type_ident where #base_type_ident: #base_bounds {
299                type Base = #base_type_ident;
300                type Dual = #base_type_ident;
301
302                #[inline]
303                fn orientation(self) -> ::linnet::half_edge::involution::Orientation {
304                    ::linnet::half_edge::involution::Orientation::Undirected
305                }
306
307                #base_repname_common_impl
308                #[inline]
309                fn is_dual(self) -> bool { true }
310                #[inline] fn matches(&self, _other: &Self::Dual) -> bool { true }
311                #[inline] fn dual(self) -> Self::Dual { self }
312            }
313        };
314        quote! {
315            impl #base_type_ident {
316                pub const NAME: &'static str = #name_lit;
317            }
318            #base_from_impl
319            #base_try_from_impl
320            #base_repname_impl
321            #base_display_impl
322        }
323    } else {
324        // ===== Dualizable Case =====
325
326        // 1. Determine the identifier for the Dual struct
327        let dual_type_ident = match &repr_attrs.custom_dual_name {
328            Some(custom_name) => custom_name.clone(),
329            None => format_ident!("Dual{}", base_type_ident, span = base_type_ident.span()),
330        };
331
332        // 2. Generate the Dual struct definition (using the filtered derives)
333        let derive_attr = if !derived_traits.is_empty() {
334            // Pass the collected Vec<Path> directly to quote
335            quote! { #[derive( #(#derived_traits),* )] }
336        } else {
337            quote! {}
338        };
339        let dual_struct_def = quote! {
340            #derive_attr
341            #vis struct #dual_type_ident #fields
342        };
343
344        // 3. Define Rep creation calls
345        let rep_new_base_call =
346            quote! { ::spenso::structure::representation::LibraryRep::new_dual(#name_lit) };
347        let rep_new_dual_call = quote! { #rep_new_base_call.expect(concat!("Failed to create dual Rep for ", #name_lit)).dual() };
348
349        // 4. Implementations for the BASE type (Remains Correct)
350        let base_from_impl = quote! {
351            impl From<#base_type_ident> for ::spenso::structure::representation::LibraryRep where #base_type_ident: Copy {
352                fn from(_value: #base_type_ident) -> Self {
353                    #rep_new_base_call.expect(concat!("Failed to create Rep for ", #name_lit))
354                }
355            }
356        };
357        let base_try_from_impl = quote! {
358            impl TryFrom<::spenso::structure::representation::LibraryRep> for #base_type_ident where #base_type_ident: Default {
359                type Error = ::spenso::structure::representation::RepresentationError;
360
361                fn try_from(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, Self::Error> {
362                    let expected_rep = #rep_new_base_call.expect(concat!("Failed to create Rep for ", #name_lit));
363                    if rep == expected_rep {
364                        ::std::result::Result::Ok(#base_type_ident::default())
365                    } else {
366                        ::std::result::Result::Err(::spenso::structure::representation::RepresentationError::WrongRepresentationError(#name_lit.to_owned(), rep.to_string()))
367                    }
368                }
369            }
370        };
371
372        let base_repname_impl = quote! {
373            impl ::spenso::structure::representation::RepName for #base_type_ident where #base_type_ident: #base_bounds, #dual_type_ident: #dual_bounds {
374                type Base = #base_type_ident;
375                type Dual = #dual_type_ident;
376
377
378                #[inline]
379                fn orientation(self) -> ::linnet::half_edge::involution::Orientation {
380                    ::linnet::half_edge::involution::Orientation::Default
381                }
382
383                #base_repname_common_impl
384                #[inline]
385                fn is_dual(self) -> bool { false }
386                #[inline]
387                fn matches(&self, _other: &Self::Dual) -> bool { true }
388                #[inline]
389                fn dual(self) -> Self::Dual where Self::Dual: Default {
390                    #dual_type_ident::default()
391                }
392            }
393        };
394        let base_impls = quote! {
395            impl #base_type_ident {
396                pub const NAME: &'static str = #name_lit;
397            }
398            #base_from_impl
399            #base_try_from_impl
400            #base_repname_impl
401            #base_display_impl
402        };
403
404        // 5. Implementations for the generated DUAL type (Remains Correct)
405        let dual_display_impl = quote! {
406            impl ::std::fmt::Display for #dual_type_ident where #dual_type_ident: Copy + Into<::spenso::structure::representation::LibraryRep> {
407                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
408                    write!(f, "{}", ::spenso::structure::representation::LibraryRep::from(*self))
409                }
410            }
411        };
412        let dual_from_impl = quote! {
413            impl From<#dual_type_ident> for ::spenso::structure::representation::LibraryRep where #dual_type_ident: Copy {
414                fn from(_value: #dual_type_ident) -> Self { #rep_new_dual_call }
415            }
416        };
417        let dual_try_from_impl = quote! {
418            impl TryFrom<::spenso::structure::representation::LibraryRep> for #dual_type_ident where #dual_type_ident: Default {
419                type Error = ::spenso::structure::representation::RepresentationError; fn try_from(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, Self::Error> {
420                    let base_rep = #rep_new_base_call.expect(concat!("Failed to create dual Rep for ", #name_lit));
421                    let expected_rep = base_rep.dual();
422                    if rep == expected_rep {
423                        ::std::result::Result::Ok(#dual_type_ident::default())
424                    } else {
425                        ::std::result::Result::Err(::spenso::structure::representation::RepresentationError::WrongRepresentationError(expected_rep.to_string(), rep.to_string()))
426                    }
427                }
428            }
429        };
430        let dual_repname_impl = quote! {
431            impl ::spenso::structure::representation::RepName for #dual_type_ident where #dual_type_ident: #dual_bounds, #base_type_ident: #base_bounds {
432                type Base = #base_type_ident;
433                type Dual = #base_type_ident;
434
435                #[inline]
436                fn orientation(self) -> ::linnet::half_edge::involution::Orientation {
437                    ::linnet::half_edge::involution::Orientation::Reversed
438                }
439                #base_repname_common_impl
440                #[inline]
441                fn dual(self) -> Self::Dual where Self::Dual: Default { #base_type_ident::default() }
442                #[inline]
443                fn is_dual(self) -> bool { true }
444                #[inline]
445                fn matches(&self, _other: &Self::Dual) -> bool { true }
446                #[inline]
447                fn is_neg(self, i: usize) -> bool where Self: Copy, Self::Dual: Copy + ::spenso::structure::representation::RepName {
448                    self.dual().is_neg(i)
449                }
450            }
451        };
452        let dual_impls = quote! {
453            #dual_from_impl
454            #dual_try_from_impl
455            #dual_repname_impl
456            #dual_display_impl
457        };
458
459        // 6. Combine generated code
460        quote! {
461            #dual_struct_def
462            #base_impls
463            #dual_impls
464        }
465    };
466
467    TokenStream::from(expanded)
468}