Skip to main content

moverox_traits_derive/
datatype.rs

1use 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        // for use in function signatures
72        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        // to use in constructing the type tag struct
88        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                /// Create this type's specialized type tag.
103                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    /// If the type tag's address, module and name are const, implement `ConstStructTag`
113    /// conditional on all type parameters implementing `ConstTypeTag`.
114    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() // We know address, module and name are const
143            .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    /// This datatype's associated type tag as `_TypeTag<T::TypeTag, U::TypeTag, ...>`, where `T,
160    /// U, ...` are this type's generic types.
161    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
199/// Check that the datatype (struct/enum) has valid generics.
200fn 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
231/// Move datatypes must have the `moverox_traits::MoveType` bound in all of its type parameters.
232fn 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
240// https://github.com/dtolnay/syn/blob/master/examples/heapsize/heapsize_derive/src/lib.rs#L36-L44
241fn 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
250/// The `TypeTag` and `StructTag` associated types have type parameters like `<T::TypeTag, ...>`.
251fn 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}