moverox_codegen/move_struct/
mod.rs

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