Skip to main content

moverox_codegen/move_struct/
mod.rs

1use std::collections::{HashMap, HashSet};
2
3use proc_macro2::{Ident, TokenStream};
4use quote::quote;
5
6mod braced;
7mod tuple;
8
9use braced::BracedStructExt as _;
10use tuple::TupleStructExt as _;
11
12use crate::generics::GenericsExt;
13use crate::{ItemContext, Result};
14
15pub(super) trait StructGen {
16    /// The full Rust struct declaration, its `new` constructor and potentially its `HasKey`
17    /// implementation.
18    fn to_rust(&self, otw_types: HashSet<Ident>, ctx: ItemContext<'_>) -> Result<TokenStream>;
19}
20
21impl StructGen for move_syn::Struct {
22    fn to_rust(&self, otw_types: HashSet<Ident>, ctx: ItemContext<'_>) -> Result<TokenStream> {
23        let decl = self.rust_declaration(otw_types, ctx)?;
24        let impl_new = self.impl_new(ctx.address_map);
25        let impl_has_key_maybe = self.impl_has_key(ctx.thecrate).unwrap_or_default();
26        Ok(quote! {
27            #decl
28            #impl_new
29            #impl_has_key_maybe
30        })
31    }
32}
33
34trait StructExt {
35    fn rust_declaration(
36        &self,
37        otw_types: HashSet<Ident>,
38        ctx: ItemContext<'_>,
39    ) -> Result<TokenStream>;
40
41    /// The Rust code for the struct's `new` constructor.
42    fn impl_new(&self, address_map: &HashMap<Ident, TokenStream>) -> TokenStream;
43
44    /// If this is a braced struct and `key` is one of its abilities, then return the
45    /// `moverox_traits::HasKey` implementation for it.
46    fn impl_has_key(&self, thecrate: &TokenStream) -> Option<TokenStream>;
47
48    /// Any additional derives to prepend to the standard ones.
49    ///
50    /// Currently only `Default` if this struct is empty. Avoids the `clippy::new_without_default`
51    /// lint.
52    fn extra_derives(&self) -> Option<TokenStream>;
53
54    /// If any generic types, `<T, ...>`, else nothing.
55    ///
56    /// No bounds on the types; this is usually for usage after the datatype identifier.
57    fn generics(&self) -> TokenStream;
58
59    /// Generics for the main struct declaration. These may have defaults set.
60    fn type_generics(
61        &self,
62        thecrate: &TokenStream,
63        otw_types: HashSet<Ident>,
64    ) -> Result<TokenStream>;
65
66    /// Identifiers of each phantom type of the struct.
67    fn phantom_types(&self) -> impl Iterator<Item = &Ident>;
68}
69
70impl StructExt for move_syn::Struct {
71    fn rust_declaration(
72        &self,
73        otw_types: HashSet<Ident>,
74        ctx: ItemContext<'_>,
75    ) -> Result<TokenStream> {
76        use move_syn::StructKind as K;
77        let Self { ident, kind, .. } = self;
78
79        let extra_attrs: TokenStream = ctx
80            .package
81            .into_iter()
82            .map(unsynn::ToTokens::to_token_stream)
83            .map(|addr| quote!(#[move_(address = #addr)]))
84            .chain(ctx.module.map(|ident| quote!(#[move_(module = #ident)])))
85            .collect();
86        let extra_derives = self.extra_derives().unwrap_or_default();
87        let type_generics = self.type_generics(ctx.thecrate, otw_types)?;
88        let contents = match kind {
89            K::Braced(braced) => braced.to_rust_contents(self.phantom_types(), ctx.address_map),
90            K::Tuple(tuple) => tuple.to_rust_contents(self.phantom_types(), ctx.address_map),
91        };
92        // NOTE: this has to be formatted as a string first, so that `quote!` will turn it into a
93        // string literal later, which is what `#[serde(crate = ...)]` accepts
94        let thecrate = ctx.thecrate;
95        let serde_crate = format!("{thecrate}::serde").replace(" ", "");
96        Ok(quote! {
97            #[derive(
98                #extra_derives
99                Clone,
100                Debug,
101                PartialEq,
102                Eq,
103                Hash,
104                #thecrate::traits::MoveDatatype,
105                #thecrate::serde::Deserialize,
106                #thecrate::serde::Serialize,
107            )]
108            #[move_(crate = #thecrate::traits)]
109            #[serde(crate = #serde_crate)]
110            #extra_attrs
111            #[allow(non_snake_case)]
112            pub struct #ident #type_generics #contents
113        })
114    }
115
116    fn impl_new(&self, address_map: &HashMap<Ident, TokenStream>) -> TokenStream {
117        use move_syn::StructKind;
118        let Self { ident, kind, .. } = self;
119        let generics = self.generics();
120        let (args, assignments) = match kind {
121            StructKind::Braced(braced) => braced.impl_new(self.phantom_types(), address_map),
122            StructKind::Tuple(tuple) => tuple.impl_new(self.phantom_types(), address_map),
123        };
124        quote! {
125            impl #generics #ident #generics {
126                #[allow(clippy::just_underscores_and_digits, clippy::too_many_arguments)]
127                pub const fn new(#args) -> Self {
128                    Self #assignments
129                }
130            }
131        }
132    }
133
134    /// Impl `moverox_traits::HasKey` if the struct has the 'key' ability.
135    ///
136    /// # Note
137    ///
138    /// This assumes the Move adapter (platform) is similar to Sui/IOTA in that:
139    /// - objects are required to have an `id: UID` field
140    /// - `UID` has a `id: ID` field
141    /// - `ID` has a `bytes: address` field
142    ///
143    /// Hence this generates code to return that innermost `bytes` field.
144    fn impl_has_key(&self, thecrate: &TokenStream) -> Option<TokenStream> {
145        use move_syn::Ability;
146        if !self.abilities().any(|a| matches!(a, Ability::Key(_))) {
147            return None;
148        }
149        let ident = &self.ident;
150        let generics = self.generics();
151        Some(quote! {
152            impl #generics #thecrate::traits::HasKey for  #ident #generics {
153                fn address(&self) -> #thecrate::types::Address {
154                    self.id.id.bytes
155                }
156            }
157        })
158    }
159
160    fn extra_derives(&self) -> Option<TokenStream> {
161        use move_syn::StructKind;
162        let is_empty = match &self.kind {
163            StructKind::Braced(braced) => braced.is_empty(),
164            StructKind::Tuple(tuple) => tuple.is_empty(),
165        };
166        is_empty.then_some(quote!(Default,))
167    }
168
169    fn generics(&self) -> TokenStream {
170        self.generics
171            .as_ref()
172            .map(GenericsExt::to_rust)
173            .unwrap_or_default()
174    }
175
176    fn type_generics(
177        &self,
178        thecrate: &TokenStream,
179        otw_types: HashSet<Ident>,
180    ) -> Result<TokenStream> {
181        self.generics
182            .as_ref()
183            .map(|g| g.type_generics(thecrate, otw_types))
184            .transpose()
185            .map(Option::unwrap_or_default)
186    }
187
188    fn phantom_types(&self) -> impl Iterator<Item = &Ident> {
189        self.generics
190            .as_ref()
191            .into_iter()
192            .flat_map(GenericsExt::phantoms)
193    }
194}