1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
use crate::util::get_single_tuple_variant;
use crate::util::ignore_enum_data;
use crate::util::index_to_u8;
use darling::FromMeta;
use proc_macro::TokenStream;
use proc_macro_error::abort;
use syn::punctuated::Punctuated;
use syn::Item;
use syn::ItemEnum;
use syn::Variant;
use syn::{parse, parse_macro_input};
/// Type for parsing the `#[hdk_to_coordinates(nested = true)]`
/// attribute into. Defaults to false.
#[derive(Debug, FromMeta)]
#[darling(derive_syn_parse)]
struct MacroArgs {
#[darling(default)]
nested: bool,
#[darling(default)]
entry: bool,
}
pub fn build(args: TokenStream, input: TokenStream) -> TokenStream {
// Parse input and attributes.
let input = parse_macro_input!(input as Item);
let attr_args: MacroArgs = match parse(args) {
Ok(v) => v,
Err(e) => {
return e.to_compile_error().into();
}
};
// Check the input is an enum.
let (ident, variants) = match &input {
Item::Enum(ItemEnum {
ident, variants, ..
}) => (ident, variants),
r => {
abort!(r, "The `hdk_to_coordinates` macro can only be used on enums."; help = "Make this an enum.";)
}
};
let entry_or_link = if attr_args.entry {
quote::quote! {EntryDefIndex}
} else {
quote::quote! {LinkType}
};
// Generate the output for mapping between variants
// and local types.
let variant_to_index = if attr_args.nested {
nesting(ident, variants, entry_or_link.clone())
} else {
no_nesting(ident, variants, entry_or_link.clone())
};
let output = quote::quote! {
#input
#variant_to_index
// Add the owned from.
impl From<#ident> for ZomeTypesKey<#entry_or_link> {
fn from(t: #ident) -> Self {
Self::from(&t)
}
}
};
output.into()
}
/// Generates output for an enum while ignoring nested enums.
fn no_nesting(
ident: &syn::Ident,
variants: &Punctuated<Variant, syn::token::Comma>,
entry_or_link: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
// Get the total number of variants for this enum.
let variant_len = index_to_u8(variants.len());
// Create match branches for each variant that map to the `ZomeTypesKey`.
let variant_to_index: proc_macro2::TokenStream = variants
.iter()
.enumerate()
.map(
|(
index,
syn::Variant {
ident: v_ident,
fields,
..
},
)| {
// Get the identifier of this variant as a u8.
let index = index_to_u8(index);
// Generate output that ignores any nested data.
let ignore = ignore_enum_data(fields);
// Generate the match branch.
quote::quote! {#ident::#v_ident #ignore => ZomeTypesKey::<#entry_or_link>{ zome_index: 0.into(), type_index: #index.into() },}
},
)
.collect();
quote::quote! {
impl From<&#ident> for ZomeTypesKey<#entry_or_link> {
fn from(t: &#ident) -> Self {
match t {
// Use the generated match branches here.
#variant_to_index
}
}
}
// Implement the `EnumLen` trait that sets the const based on the
// number of variants.
impl EnumLen for #ident {
const ENUM_LEN: u8 = #variant_len;
}
// Implement a const len function to
// give this enum a length.
impl #ident {
pub const fn len() -> u8 {
<Self as EnumLen>::ENUM_LEN
}
}
}
}
/// Generates output for an enum while ignoring nested enums.
fn nesting(
ident: &syn::Ident,
variants: &Punctuated<Variant, syn::token::Comma>,
entry_or_link: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
// Generate inner match arms for `impl From<&Self> for ZomeTypesKey`
let inner_from: proc_macro2::TokenStream = variants
.iter()
.enumerate()
.map(
|(
enum_index,
syn::Variant {
ident: v_ident,
fields,
..
},
)| {
// Get this variants index as u8.
let enum_index = index_to_u8(enum_index);
// Map inner fields to a `ZomeTypesKey`.
match fields {
syn::Fields::Named(syn::FieldsNamed { named, .. }) =>
// Get the first fields identifier.
match named.iter().next().and_then(|syn::Field{ident, ..}| ident.as_ref())
{
Some(inner_ident) => {
quote::quote! {
#ident::#v_ident { #inner_ident, ..} => Self{ zome_index: #enum_index.into(), type_index: Self::from(#inner_ident).type_index },
}
}
None => abort!(v_ident, "Struct style enum needs at least one field."),
}
syn::Fields::Unnamed(syn::FieldsUnnamed { .. }) => {
// Check there is only a single tuple variant.
get_single_tuple_variant(v_ident, fields);
quote::quote! {
#ident::#v_ident (inner_ident) => Self{ zome_index: #enum_index.into(), type_index: Self::from(inner_ident).type_index },
}
}
syn::Fields::Unit => {
// A unit variant is simply the starting variant point.
quote::quote! {
#ident::#v_ident => Self{ zome_index: #enum_index.into(), type_index: 0.into() },
}
},
}
},
)
.collect();
// Implement the `EnumVariantLen` trait for each variant.
let enum_variant_len: proc_macro2::TokenStream = variants
.iter()
.enumerate()
.map(|(enum_index, syn::Variant { fields, .. })| {
// Get this variants index as a u8.
let enum_index = index_to_u8(enum_index);
// Get the nested field if there is one.
let nested_field = match fields {
syn::Fields::Named(syn::FieldsNamed { named, .. }) => named.iter().next(),
syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }) => unnamed.iter().next(),
syn::Fields::Unit => None,
};
// Set the start of this variants length.
let start = if enum_index == 0 {
quote::quote! {0;}
} else {
// The start for 1.. variants is the length as of n - 1.
quote::quote! {<Self as EnumVariantLen<{#enum_index - 1}>>::ENUM_VARIANT_LEN;}
};
let inner_len = match nested_field {
Some(syn::Field {
ty: syn::Type::Path(syn::TypePath { path, .. }),
..
}) => {
// For a nested field the inner length is the inner fields
// EnumLen::ENUM_LEN.
quote::quote! {
const ENUM_VARIANT_INNER_LEN: u8 = #path::ENUM_LEN;
}
}
// Unit enums have an inner length of one.
None => quote::quote! {
const ENUM_VARIANT_INNER_LEN: u8 = 1;
},
_ => abort!(
nested_field,
"The field for this enum has an invalid inner type for this macro."
),
};
// Note that `ENUM_VARIANT_LEN` is the total length up to this variant
// where as `ENUM_VARIANT_INNER_LEN` is the actual length of the inner field.
// Implement the `EnumVariantLen` for this variant.
quote::quote! {
impl EnumVariantLen<#enum_index> for #ident {
const ENUM_VARIANT_START: u8 = #start
#inner_len
}
}
})
.collect();
// Get the index of the last variant (len - 1) as a u8.
let i = variants
.len()
.checked_sub(1)
.unwrap_or_else(|| abort!(ident, "Enum must have at least one variant"));
let last_variant_index = index_to_u8(i);
quote::quote! {
// The overall length of this enum is the variant length
// as of the last variant.
impl EnumLen for #ident {
const ENUM_LEN: u8 = <#ident as EnumVariantLen<#last_variant_index>>::ENUM_VARIANT_LEN;
}
#enum_variant_len
impl From<&#ident> for ZomeTypesKey<#entry_or_link> {
fn from(n: &#ident) -> Self {
match n {
#inner_from
}
}
}
// Impl simple const len helper.
impl #ident {
pub const fn len() -> u8 {
<Self as EnumLen>::ENUM_LEN
}
}
}
}