1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{ToTokens, format_ident, quote}; use syn::{
5 Attribute,
6 Data,
7 DeriveInput,
8 Expr,
9 ExprLit,
10 ExprPath,
11 Ident,
12 Lit,
13 LitStr,
14 Meta,
15 Path, Token,
17 parse::Parser,
18 parse_macro_input,
19 punctuated::Punctuated,
20 spanned::Spanned,
21};
22
23#[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 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(), "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
161fn 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 if attr.path().is_ident("derive") {
168 match attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated) {
170 Ok(nested_metas) => {
171 for meta in nested_metas {
173 if let Meta::Path(path) = meta {
175 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); }
184 } else {
185 return Err(syn::Error::new_spanned(
189 meta, "Expected simple trait paths (e.g., Debug, Clone) in derive attribute, found other meta item.",
191 ));
192 }
193 }
194 }
195 Err(e) => {
196 return Err(syn::Error::new_spanned(
199 attr.to_token_stream(), 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 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 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 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 let base_bounds = quote! { Default + Copy };
248 let dual_bounds = quote! { Default + Copy };
249
250 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 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 let expanded = if is_self_dual {
269 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 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 let derive_attr = if !derived_traits.is_empty() {
334 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 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 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 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 quote! {
461 #dual_struct_def
462 #base_impls
463 #dual_impls
464 }
465 };
466
467 TokenStream::from(expanded)
468}