moverox_traits_derive/
datatype.rs1use darling::FromDeriveInput as _;
2use proc_macro2::TokenStream;
3use quote::quote;
4use syn::punctuated::Punctuated;
5use syn::spanned::Spanned as _;
6use syn::{DeriveInput, GenericParam, Generics, Path, Token, TypeParamBound, parse_quote};
7
8use crate::attributes::MoveAttributes;
9use crate::type_tag::TypeTagStruct;
10
11pub(crate) struct Datatype {
12 pub(crate) type_tag: TypeTagStruct,
13 value: DeriveInput,
14}
15
16impl Datatype {
17 pub(crate) fn parse(item: TokenStream) -> syn::Result<Self> {
18 let ast: DeriveInput = syn::parse2(item)?;
19 ensure_nonempty_struct(&ast)?;
20 let attrs = MoveAttributes::from_derive_input(&ast)?;
21 validate_datatype_generics(&ast.generics)?;
22 let type_tag = TypeTagStruct::new(&ast, &attrs);
23 Ok(Self {
24 type_tag,
25 value: ast,
26 })
27 }
28
29 pub(crate) fn impl_move_datatype(&self) -> TokenStream {
30 let Self {
31 type_tag,
32 value: ast,
33 } = self;
34 let TypeTagStruct {
35 ident: type_tag_ident,
36 thecrate,
37 ..
38 } = type_tag;
39
40 let type_tag_type = {
41 let type_generics = type_arguments_in_associated_type(&ast.generics);
42 quote!(#type_tag_ident < #type_generics >)
43 };
44
45 let ident = &ast.ident;
46 let generics = add_type_bound(ast.generics.clone(), parse_quote!(#thecrate::MoveType));
47 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
48
49 quote! {
50 impl #impl_generics #thecrate::MoveType for #ident #type_generics #where_clause {
51 type TypeTag = #type_tag_type;
52 }
53
54 impl #impl_generics #thecrate::MoveDatatype for #ident #type_generics #where_clause {
55 type StructTag = #type_tag_type;
56 }
57 }
58 }
59
60 pub(crate) fn impl_type_tag_constructor(&self) -> TokenStream {
61 let Self {
62 type_tag,
63 value: ast,
64 } = self;
65 let TypeTagStruct {
66 ident: type_tag_ident,
67 thecrate,
68 ..
69 } = type_tag;
70
71 let type_tag_fn_args: Vec<_> = type_tag
73 .non_type_fields()
74 .into_iter()
75 .filter_map(|f| {
76 let name = f.ident?;
77 let ty = f.ty;
78 Some(quote!(#name: #ty))
79 })
80 .chain(type_tag.type_fields().into_iter().filter_map(|f| {
81 let name = f.ident?;
82 let ty = f.ty;
83 Some(quote!(#name: #ty::TypeTag))
84 }))
85 .collect();
86
87 let type_tag_field_names: Vec<_> = type_tag
89 .fields()
90 .into_iter()
91 .filter_map(|f| f.ident)
92 .collect();
93
94 let ident = &ast.ident;
95 let generics = add_type_bound(ast.generics.clone(), parse_quote!(#thecrate::MoveType));
96 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
97
98 let type_tag_type = self.type_tag_type();
99
100 quote! {
101 impl #impl_generics #ident #type_generics #where_clause {
102 pub const fn type_tag(#(#type_tag_fn_args),*) -> #type_tag_type {
104 #type_tag_ident {
105 #(#type_tag_field_names),*
106 }
107 }
108 }
109 }
110 }
111
112 pub(crate) fn impl_const_struct_tag(&self) -> Option<TokenStream> {
115 let Self {
116 type_tag:
117 TypeTagStruct {
118 ident: type_tag_ident,
119 address,
120 module,
121 name,
122 thecrate,
123 ..
124 },
125 value,
126 } = self;
127 if address.is_none() || module.is_none() || name.is_none() {
128 return None;
129 }
130
131 let ident = &value.ident;
132 let generics = add_type_bound(
133 value.generics.clone(),
134 parse_quote!(#thecrate::ConstTypeTag),
135 );
136 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
137
138 let type_tag_type = self.type_tag_type();
139
140 let type_tag_constructor = self
141 .type_tag
142 .type_fields() .into_iter()
144 .filter_map(|f| {
145 let name = f.ident?;
146 let ty = f.ty;
147 Some(quote!(#name: <#ty as #thecrate::ConstTypeTag>::TYPE_TAG))
148 });
149
150 Some(quote! {
151 impl #impl_generics #thecrate::ConstStructTag for #ident #type_generics #where_clause {
152 const STRUCT_TAG: #type_tag_type = #type_tag_ident {
153 #(#type_tag_constructor),*
154 };
155 }
156 })
157 }
158
159 fn type_tag_type(&self) -> TokenStream {
162 let type_tag_ident = &self.type_tag.ident;
163
164 let type_generics = type_arguments_in_associated_type(&self.value.generics);
165 quote!(#type_tag_ident < #type_generics >)
166 }
167}
168
169fn ensure_nonempty_struct(ast: &DeriveInput) -> syn::Result<()> {
170 match &ast.data {
171 syn::Data::Struct(data) => {
172 if data.fields.is_empty() {
173 return Err(syn::Error::new(
174 data.fields.span(),
175 "Structs can't be empty. If a Move struct is empty, then in the Rust equivalent it \
176 must have a single field of type `bool`. This is because the BCS of an empty Move \
177 struct encodes a single boolean dummy field.",
178 ));
179 }
180 }
181 syn::Data::Enum(data) => {
182 if data.variants.is_empty() {
183 return Err(syn::Error::new(
184 data.variants.span(),
185 "A Move 'enum' must define at least one variant",
186 ));
187 }
188 }
189 _ => {
190 return Err(syn::Error::new(
191 ast.span(),
192 "MoveDatatype only defined for structs",
193 ));
194 }
195 };
196 Ok(())
197}
198
199fn validate_datatype_generics(generics: &Generics) -> syn::Result<()> {
201 use syn::TypeParamBound;
202
203 for param in &generics.params {
204 match param {
205 GenericParam::Type(type_param) => {
206 if type_param.bounds.iter().all(|bound| {
207 matches!(
208 bound,
209 TypeParamBound::Trait(trait_bound) if expected_trait_bound(trait_bound)
210 )
211 }) {
212 continue;
213 }
214 return Err(syn::Error::new_spanned(
215 type_param,
216 "Move datatypes can at most have the `moverox_traits::MoveType` bound on its \
217 type parameters",
218 ));
219 }
220 _ => {
221 return Err(syn::Error::new_spanned(
222 param,
223 "Only Type generics are supported",
224 ));
225 }
226 }
227 }
228 Ok(())
229}
230
231fn expected_trait_bound(bound: &syn::TraitBound) -> bool {
233 matches!(bound.modifier, syn::TraitBoundModifier::None)
234 && bound.lifetimes.is_none()
235 && bound.path.segments.last().is_some_and(|ps| {
236 ps.ident == "MoveType" && matches!(ps.arguments, syn::PathArguments::None)
237 })
238}
239
240fn add_type_bound(mut generics: Generics, bound: TypeParamBound) -> Generics {
242 for param in &mut generics.params {
243 if let GenericParam::Type(ref mut type_param) = *param {
244 type_param.bounds.push(bound.clone());
245 }
246 }
247 generics
248}
249
250fn type_arguments_in_associated_type(generics: &Generics) -> Punctuated<Path, Token![,]> {
252 use syn::GenericParam as G;
253 let idents = generics.params.iter().filter_map(|p| {
254 if let G::Type(t) = p {
255 Some(&t.ident)
256 } else {
257 None
258 }
259 });
260 parse_quote!(#(#idents::TypeTag),*)
261}