1use quote::quote;
2use syn::{Data, DeriveInput, Error, Fields, Path, spanned::Spanned};
3
4pub fn derive_tagged_links(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
5 let fields = match &input.data {
6 Data::Struct(ds) => match &ds.fields {
7 Fields::Named(named) => &named.named,
8 _ => {
9 return Err(Error::new(
10 ds.fields.span(),
11 "TaggedLinks only supports structs with named fields",
12 ));
13 }
14 },
15 _ => {
16 return Err(Error::new(
17 input.span(),
18 "TaggedLinks can only be derived for structs",
19 ));
20 }
21 };
22
23 let rbtree_impls = impl_rbtree(input, fields)?;
24 let list_impls = impl_list(input, fields)?;
25
26 Ok(quote! {
27 #rbtree_impls
28 #list_impls
29 })
30}
31
32fn impl_rbtree(
33 input: &DeriveInput,
34 fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
35) -> syn::Result<proc_macro2::TokenStream> {
36 let struct_ident = &input.ident;
37 let generics = &input.generics;
38
39 let mut impls = Vec::new();
40
41 for field in fields {
42 let Some(field_ident) = field.ident.clone() else {
43 continue;
44 };
45
46 if let (Some(tag_path), Some(idx_path)) = find_tagged(&field.attrs, "rbtree")? {
47 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
48
49 let impl_block = quote! {
50 impl #impl_generics crate::types::rbtree::Linkable<#tag_path, #idx_path> for #struct_ident #ty_generics #where_clause {
51 #[inline]
52 fn links(&self) -> &crate::types::rbtree::Links<#tag_path, #idx_path> {
53 &self.#field_ident
54 }
55 #[inline]
56 fn links_mut(&mut self) -> &mut crate::types::rbtree::Links<#tag_path, #idx_path> {
57 &mut self.#field_ident
58 }
59 }
60 };
61
62 impls.push(impl_block);
63 }
64 }
65
66 Ok(quote! { #(#impls)* })
67}
68
69fn impl_list(
70 input: &DeriveInput,
71 fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
72) -> syn::Result<proc_macro2::TokenStream> {
73 let struct_ident = &input.ident;
74 let generics = &input.generics;
75
76 let mut impls = Vec::new();
77
78 for field in fields {
79 let Some(field_ident) = field.ident.clone() else {
80 continue;
81 };
82
83 if let (Some(tag_path), Some(idx_path)) = find_tagged(&field.attrs, "list")? {
84 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
85
86 let impl_block = quote! {
87 impl #impl_generics crate::types::list::Linkable<#tag_path, #idx_path> for #struct_ident #ty_generics #where_clause {
88 #[inline]
89 fn links(&self) -> &crate::types::list::Links<#tag_path, #idx_path> {
90 &self.#field_ident
91 }
92 #[inline]
93 fn links_mut(&mut self) -> &mut crate::types::list::Links<#tag_path, #idx_path> {
94 &mut self.#field_ident
95 }
96 }
97 };
98
99 impls.push(impl_block);
100 }
101 }
102
103 Ok(quote! { #(#impls)* })
104}
105
106fn find_tagged(
107 attrs: &[syn::Attribute],
108 attr_name: &str,
109) -> syn::Result<(Option<Path>, Option<Path>)> {
110 for attr in attrs {
111 if !attr.path().is_ident(attr_name) {
112 continue;
113 }
114
115 let mut tag: Option<Path> = None;
116 let mut idx: Option<Path> = None;
117
118 attr.parse_nested_meta(|meta| {
119 if meta.path.is_ident("tag") {
120 let value = meta.value()?; let p: Path = value.parse()?;
122 tag = Some(p);
123 Ok(())
124 } else if meta.path.is_ident("idx") {
125 let value = meta.value()?; let p: Path = value.parse()?;
127 idx = Some(p);
128 Ok(())
129 } else {
130 Err(meta.error("expected `tag = SomePath` or `idx = SomePath`"))
131 }
132 })?;
133
134 return Ok((tag, idx));
135 }
136 Ok((None, None))
137}