spenso_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{format_ident, quote, ToTokens}; // Ensure ToTokens is imported
4use syn::{
5    parse::Parser,
6    parse_macro_input,
7    punctuated::Punctuated,
8    spanned::Spanned,
9    Attribute,
10    Data,
11    DeriveInput,
12    Expr,
13    ExprLit,
14    ExprPath,
15    Ident,
16    Lit,
17    LitStr,
18    Meta,
19    Path, // Use Path and Meta
20    Token,
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!("Failed to parse derive arguments: {}. Check syntax inside #[derive(...)].", e),
201                     ));
202                }
203            }
204        }
205    }
206
207    Ok(derived_traits)
208}
209
210#[proc_macro_derive(SimpleRepresentation, attributes(representation))]
211pub fn derive_simple_representation(input: TokenStream) -> TokenStream {
212    let input = parse_macro_input!(input as DeriveInput);
213
214    // --- Extract Struct Info ---
215    let fields = match &input.data {
216        Data::Struct(s) => s.fields.clone(),
217        _ => {
218            return syn::Error::new_spanned(
219                &input.ident,
220                "SimpleRepresentation can only be derived for structs",
221            )
222            .to_compile_error()
223            .into();
224        }
225    };
226
227    // --- Parse Attributes ---
228    let vis = &input.vis;
229    let repr_attrs = match parse_representation_attributes(&input.attrs) {
230        Ok(attrs) => attrs,
231        Err(e) => return e.to_compile_error().into(),
232    };
233    // Use the revised helper function
234    let derived_traits = match get_filtered_derive_paths(&input.attrs) {
235        Ok(traits) => traits,
236        Err(e) => return e.to_compile_error().into(),
237    };
238
239    let base_type_ident = &input.ident;
240    let name_lit = &repr_attrs.name;
241    let is_self_dual = repr_attrs.is_self_dual;
242
243    // --- Bounds needed for RepName impls ---
244    let base_bounds = quote! { Default + Copy };
245    let dual_bounds = quote! { Default + Copy };
246
247    // --- Common RepName Implementation Parts ---
248    let base_repname_common_impl = quote! {
249        #[inline]
250        fn from_library_rep(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, ::spenso::structure::representation::RepresentationError>{
251            rep.try_into()
252        }
253        #[inline] fn base(&self) -> Self::Base where Self::Base: Default { Self::Base::default() }
254        #[inline] fn is_base(&self) -> bool { ::std::any::TypeId::of::<Self>() == ::std::any::TypeId::of::<Self::Base>() }
255    };
256
257    // --- Display Impls ---
258    let base_display_impl = quote! {
259        impl ::std::fmt::Display for #base_type_ident where #base_type_ident: Copy + Into<::spenso::structure::representation::LibraryRep> {
260            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { write!(f, "{}", ::spenso::structure::representation::LibraryRep::from(*self)) }
261        }
262    };
263
264    // --- Branch based on duality ---
265    let expanded = if is_self_dual {
266        // ===== Self-Dual Case ===== (Remains Correct)
267        let rep_new_call =
268            quote! { ::spenso::structure::representation::LibraryRep::new_self_dual(#name_lit) };
269        let base_from_impl = quote! {
270            impl From<#base_type_ident> for ::spenso::structure::representation::LibraryRep
271                where #base_type_ident: Copy
272                {
273                    fn from(_value: #base_type_ident) -> Self {
274                        #rep_new_call.expect(concat!("Failed to create self-dual Rep for ", #name_lit))
275                    }
276                }
277        };
278
279        let base_try_from_impl = quote! {
280            impl TryFrom<::spenso::structure::representation::LibraryRep> for #base_type_ident where #base_type_ident: Default {
281                type Error = ::spenso::structure::representation::RepresentationError;
282
283                fn try_from(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, Self::Error>   {
284                    let expected_rep = #rep_new_call.expect(concat!("Failed to create self-dual Rep for ", #name_lit));
285                    if rep == expected_rep {
286                        ::std::result::Result::Ok(#base_type_ident::default())
287                    } else {
288                        ::std::result::Result::Err(::spenso::structure::representation::RepresentationError::WrongRepresentationError(#name_lit.to_owned(), rep.to_string()))
289                    }
290                }
291            }
292        };
293
294        let base_repname_impl = quote! {
295            impl ::spenso::structure::representation::RepName for #base_type_ident where #base_type_ident: #base_bounds {
296                type Base = #base_type_ident;
297                type Dual = #base_type_ident;
298
299                #[inline]
300                fn orientation(self) -> ::linnet::half_edge::involution::Orientation {
301                    ::linnet::half_edge::involution::Orientation::Undirected
302                }
303
304                #base_repname_common_impl
305                #[inline]
306                fn is_dual(self) -> bool { true }
307                #[inline] fn matches(&self, _other: &Self::Dual) -> bool { true }
308                #[inline] fn dual(self) -> Self::Dual { self }
309            }
310        };
311        quote! {
312            impl #base_type_ident {
313                pub const NAME: &'static str = #name_lit;
314            }
315            #base_from_impl
316            #base_try_from_impl
317            #base_repname_impl
318            #base_display_impl
319        }
320    } else {
321        // ===== Dualizable Case =====
322
323        // 1. Determine the identifier for the Dual struct
324        let dual_type_ident = match &repr_attrs.custom_dual_name {
325            Some(custom_name) => custom_name.clone(),
326            None => format_ident!("Dual{}", base_type_ident, span = base_type_ident.span()),
327        };
328
329        // 2. Generate the Dual struct definition (using the filtered derives)
330        let derive_attr = if !derived_traits.is_empty() {
331            // Pass the collected Vec<Path> directly to quote
332            quote! { #[derive( #(#derived_traits),* )] }
333        } else {
334            quote! {}
335        };
336        let dual_struct_def = quote! {
337            #derive_attr
338            #vis struct #dual_type_ident #fields
339        };
340
341        // 3. Define Rep creation calls
342        let rep_new_base_call =
343            quote! { ::spenso::structure::representation::LibraryRep::new_dual(#name_lit) };
344        let rep_new_dual_call = quote! { #rep_new_base_call.expect(concat!("Failed to create dual Rep for ", #name_lit)).dual() };
345
346        // 4. Implementations for the BASE type (Remains Correct)
347        let base_from_impl = quote! {
348            impl From<#base_type_ident> for ::spenso::structure::representation::LibraryRep where #base_type_ident: Copy {
349                fn from(_value: #base_type_ident) -> Self {
350                    #rep_new_base_call.expect(concat!("Failed to create Rep for ", #name_lit))
351                }
352            }
353        };
354        let base_try_from_impl = quote! {
355            impl TryFrom<::spenso::structure::representation::LibraryRep> for #base_type_ident where #base_type_ident: Default {
356                type Error = ::spenso::structure::representation::RepresentationError;
357
358                fn try_from(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, Self::Error> {
359                    let expected_rep = #rep_new_base_call.expect(concat!("Failed to create Rep for ", #name_lit));
360                    if rep == expected_rep {
361                        ::std::result::Result::Ok(#base_type_ident::default())
362                    } else {
363                        ::std::result::Result::Err(::spenso::structure::representation::RepresentationError::WrongRepresentationError(#name_lit.to_owned(), rep.to_string()))
364                    }
365                }
366            }
367        };
368
369        let base_repname_impl = quote! {
370            impl ::spenso::structure::representation::RepName for #base_type_ident where #base_type_ident: #base_bounds, #dual_type_ident: #dual_bounds {
371                type Base = #base_type_ident;
372                type Dual = #dual_type_ident;
373
374
375                #[inline]
376                fn orientation(self) -> ::linnet::half_edge::involution::Orientation {
377                    ::linnet::half_edge::involution::Orientation::Default
378                }
379
380                #base_repname_common_impl
381                #[inline]
382                fn is_dual(self) -> bool { false }
383                #[inline]
384                fn matches(&self, _other: &Self::Dual) -> bool { true }
385                #[inline]
386                fn dual(self) -> Self::Dual where Self::Dual: Default {
387                    #dual_type_ident::default()
388                }
389            }
390        };
391        let base_impls = quote! {
392            impl #base_type_ident {
393                pub const NAME: &'static str = #name_lit;
394            }
395            #base_from_impl
396            #base_try_from_impl
397            #base_repname_impl
398            #base_display_impl
399        };
400
401        // 5. Implementations for the generated DUAL type (Remains Correct)
402        let dual_display_impl = quote! {
403            impl ::std::fmt::Display for #dual_type_ident where #dual_type_ident: Copy + Into<::spenso::structure::representation::LibraryRep> {
404                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
405                    write!(f, "{}", ::spenso::structure::representation::LibraryRep::from(*self))
406                }
407            }
408        };
409        let dual_from_impl = quote! {
410            impl From<#dual_type_ident> for ::spenso::structure::representation::LibraryRep where #dual_type_ident: Copy {
411                fn from(_value: #dual_type_ident) -> Self { #rep_new_dual_call }
412            }
413        };
414        let dual_try_from_impl = quote! {
415            impl TryFrom<::spenso::structure::representation::LibraryRep> for #dual_type_ident where #dual_type_ident: Default {
416                type Error = ::spenso::structure::representation::RepresentationError; fn try_from(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, Self::Error> {
417                    let base_rep = #rep_new_base_call.expect(concat!("Failed to create dual Rep for ", #name_lit));
418                    let expected_rep = base_rep.dual();
419                    if rep == expected_rep {
420                        ::std::result::Result::Ok(#dual_type_ident::default())
421                    } else {
422                        ::std::result::Result::Err(::spenso::structure::representation::RepresentationError::WrongRepresentationError(expected_rep.to_string(), rep.to_string()))
423                    }
424                }
425            }
426        };
427        let dual_repname_impl = quote! {
428            impl ::spenso::structure::representation::RepName for #dual_type_ident where #dual_type_ident: #dual_bounds, #base_type_ident: #base_bounds {
429                type Base = #base_type_ident;
430                type Dual = #base_type_ident;
431
432                #[inline]
433                fn orientation(self) -> ::linnet::half_edge::involution::Orientation {
434                    ::linnet::half_edge::involution::Orientation::Reversed
435                }
436                #base_repname_common_impl
437                #[inline]
438                fn dual(self) -> Self::Dual where Self::Dual: Default { #base_type_ident::default() }
439                #[inline]
440                fn is_dual(self) -> bool { true }
441                #[inline]
442                fn matches(&self, _other: &Self::Dual) -> bool { true }
443                #[inline]
444                fn is_neg(self, i: usize) -> bool where Self: Copy, Self::Dual: Copy + ::spenso::structure::representation::RepName {
445                    self.dual().is_neg(i)
446                }
447            }
448        };
449        let dual_impls = quote! {
450            #dual_from_impl
451            #dual_try_from_impl
452            #dual_repname_impl
453            #dual_display_impl
454        };
455
456        // 6. Combine generated code
457        quote! {
458            #dual_struct_def
459            #base_impls
460            #dual_impls
461        }
462    };
463
464    TokenStream::from(expanded)
465}