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
use proc_macro2::TokenStream;
use quote::{quote, TokenStreamExt};
use syn::{spanned::Spanned, DataEnum, DeriveInput};
use crate::{
attribute::{parse_attributes, AttrLocation, Attribute},
DeriveKind, RESERVED_NAMES,
};
use super::{pack_fields, PackFields};
/// Generate impl MsgPack for an enum
pub fn derive_pack_enum(input: &DeriveInput, data: &DataEnum) -> syn::Result<TokenStream> {
let enum_name = &input.ident;
let attributes = parse_attributes(&input.attrs, AttrLocation::Enum, DeriveKind::MsgPack)?;
let untagged = attributes.contains(&Attribute::Untagged);
if RESERVED_NAMES.contains(&enum_name.to_string().as_str()) {
return Err(syn::Error::new(
input.ident.span(),
"MsgPack: reserved identifier",
));
}
// TODO: where-clause for enums
if let Some(where_clause) = &input.generics.where_clause {
return Err(syn::Error::new(
where_clause.span(),
"derive(MsgPack) doesn't support where-clauses for enums yet",
));
}
let generics = &input.generics;
let mut iter_enum_generics = quote! {};
let mut iter_enum_variants = quote! {};
let mut iter_enum_bounds = quote! {};
let mut iter_enum_match = quote! {};
let mut pack_variants = quote! {};
let mut writer_pack_variants = quote! {};
let mut writer_pack_variant_headers = quote! {};
for variant in &data.variants {
let variant_name = &variant.ident;
let variant_name_str = variant_name.to_string();
// TODO: for if/when we wan't to support serializing by discriminant, instead of name
//if let Some((_, explicit_discriminant)) = &variant.discriminant {
// // TODO: handle explicitly setting the discriminant.
// // This loop should track the discriminant in the same fashion as rustc
// match explicit_discriminant {
// syn::Expr::Lit(_lit) => {
// return quote_spanned! {
// explicit_discriminant.span() =>
// compile_error!("not supported (yet) by derive(MsgUnpack)");
// };
// }
// _ => {
// return quote_spanned! {
// explicit_discriminant.span() =>
// compile_error!("not supported by derive(MsgUnpack)");
// };
// }
// }
//}
// generate stuff for the iterator enum
iter_enum_generics.append_all(quote! {#variant_name,});
iter_enum_variants.append_all(quote! {#variant_name(#variant_name),});
iter_enum_bounds
.append_all(quote! {#variant_name: Iterator<Item = ::msgpck::Piece<'a>>,});
iter_enum_match.append_all(quote! {
Self::#variant_name(inner_iter) => inner_iter.next(),
});
// generate the actual iterator
let PackFields {
pack_fields,
write_pack_fields,
match_fields,
unit,
} = pack_fields(&variant.fields, AttrLocation::EnumVariantField)?;
let pack = if untagged && unit {
// untagged variants with no fields are serialized as null
quote! { ::core::iter::once(::msgpck::Marker::Null.into()) }
} else if untagged {
quote! { ::core::iter::empty() #pack_fields }
} else if unit {
quote! {
::msgpck::helpers::pack_enum_header(::msgpck::EnumHeader {
variant: #variant_name_str.into(),
unit: #unit,
})
}
} else {
quote! {
::msgpck::helpers::pack_enum_header(::msgpck::EnumHeader {
variant: #variant_name_str.into(),
unit: #unit,
})
#pack_fields
}
};
pack_variants.append_all(quote! {
Self::#variant_name #match_fields => {
__MsgpackerIter::#variant_name(#pack)
}
});
let write_pack = if untagged && unit {
// untagged variants with no fields are serialized as null
quote! {
__msgpck_n += 1;
__msgpck_w.write_all(&[::msgpck::Marker::Null.to_u8()])?;
}
} else if untagged {
write_pack_fields
} else {
writer_pack_variant_headers.append_all(quote! {
Self::#variant_name #match_fields =>::msgpck::EnumHeader {
variant: #variant_name_str.into(),
unit: #unit,
},
});
if unit {
quote! {}
} else {
write_pack_fields
}
};
writer_pack_variants.append_all(quote! {
Self::#variant_name #match_fields => {
#write_pack
}
});
}
if !untagged {
writer_pack_variant_headers = quote! {
// create and serialize enum header
let header = match self {
#writer_pack_variant_headers
};
__msgpck_n += ::msgpck::helpers::pack_enum_header_to_writer(header, __msgpck_w)?;
}
} else {
writer_pack_variant_headers = quote! {
// enum is marked as untagged, so we don't serialize the enum header
}
}
Ok(quote! {
#[automatically_derived]
impl #generics ::msgpck::MsgPack for #enum_name #generics {
fn pack(&self) -> impl Iterator<Item = ::msgpck::Piece<'_>> {
// Because we need different msgpack iterator types for each variant, we need a new
// enum type that impls Iterator to contain them. To avoid naming the inner iterator
// types, we use generics. It's not the prettiest, but it works.
enum __MsgpackerIter<#iter_enum_generics> {
#iter_enum_variants
}
impl<'a, #iter_enum_bounds> Iterator for __MsgpackerIter<#iter_enum_generics> {
type Item = ::msgpck::Piece<'a>;
// the implementation of next simply forwards to the inner iterators
fn next(&mut self) -> Option<Self::Item> {
match self {
#iter_enum_match
}
}
}
// This match statment returns a __MsgpackerIter
match self {
#pack_variants
}
}
fn pack_with_writer(&self, __msgpck_w: &mut dyn ::msgpck::Write)
-> Result<usize, ::msgpck::PackErr>
{
let mut __msgpck_n = 0usize;
#writer_pack_variant_headers
// serialize variant fields
match self {
#writer_pack_variants
}
Ok(__msgpck_n)
}
}
})
}