Skip to main content

moverox_codegen/
lib.rs

1#![cfg_attr(nightly, feature(doc_cfg))]
2
3//! Generate Rust code from Move IR parsed by move-syn.
4//!
5//! Defines extension traits to generate Rust code from Move intermediate representation.
6//!
7//! `thecrate` in arguments here is the path to a crate/module which exports:
8//! - a `types` module with `Address` and `U256` types from `moverox-types`
9//! - a `traits` module with `HasKey`, `MoveDatatype` and `MoveType` traits from `moverox-traits`
10//! - the `serde` crate
11
12use std::collections::HashMap;
13
14use move_syn::{Attributes, Item, Module};
15use proc_macro2::{Ident, TokenStream};
16use quote::quote;
17use unsynn::LiteralString;
18
19mod attributes;
20mod generics;
21mod iter;
22mod move_enum;
23mod move_struct;
24mod move_type;
25mod named_fields;
26mod positional_fields;
27#[cfg(test)]
28mod tests;
29
30use self::move_struct::StructGen as _;
31
32type BoxError = Box<dyn std::error::Error + 'static>;
33type Result<T = (), E = BoxError> = std::result::Result<T, E>;
34
35/// A Move module's oxidized datatypes, split into placeable pieces.
36///
37/// Lets the body be merged into an existing `mod` of the same name (e.g. one also holding PTB
38/// bindings) rather than forced into its own `pub mod`. Produced by [`ModuleGen::to_parts`];
39/// [`ModuleGen::to_rust`] wraps it back up.
40pub struct ModuleParts {
41    /// The module identifier.
42    pub ident: Ident,
43    /// The module's doc attributes.
44    pub docs: TokenStream,
45    /// What goes *inside* `pub mod <ident>`: the `address`/`u256`/`vector` type aliases followed
46    /// by the generated `struct`/`enum` datatypes.
47    pub body: TokenStream,
48}
49
50#[sealed::sealed]
51pub trait ModuleGen {
52    /// Generate the module's datatypes as a standalone `pub mod <ident> { .. }`.
53    fn to_rust(
54        &self,
55        thecrate: &TokenStream,
56        package: Option<&LiteralString>,
57        address_map: &HashMap<Ident, TokenStream>,
58    ) -> Result<TokenStream>;
59
60    /// Generate the module's datatypes as placeable [`ModuleParts`], so a caller can weave the
61    /// body into a `mod` of its own construction (e.g. alongside other generated items for the
62    /// same Move module).
63    fn to_parts(
64        &self,
65        thecrate: &TokenStream,
66        package: Option<&LiteralString>,
67        address_map: &HashMap<Ident, TokenStream>,
68    ) -> Result<ModuleParts>;
69}
70
71#[sealed::sealed]
72impl ModuleGen for Module {
73    fn to_rust(
74        &self,
75        thecrate: &TokenStream,
76        package: Option<&LiteralString>,
77        address_map: &HashMap<Ident, TokenStream>,
78    ) -> Result<TokenStream> {
79        let ModuleParts { ident, docs, body } = self.to_parts(thecrate, package, address_map)?;
80        Ok(quote! {
81            #docs
82            #[allow(rustdoc::all, clippy::too_long_first_doc_paragraph)]
83            pub mod #ident {
84                #body
85            }
86        })
87    }
88
89    fn to_parts(
90        &self,
91        thecrate: &TokenStream,
92        package: Option<&LiteralString>,
93        address_map: &HashMap<Ident, TokenStream>,
94    ) -> Result<ModuleParts> {
95        let (docs, other) = crate::attributes::extract(&self.attrs)
96            .map_err(|err| format!("Parsing `moverox` attributes: {err}"))?;
97
98        if !other.is_empty() {
99            return Err("Move modules cannot have custom `moverox` attributes".into());
100        }
101
102        let ident = self.ident.clone();
103        let item_ctx = ItemContext {
104            thecrate,
105            package,
106            module: Some(&ident),
107            address_map,
108        };
109        let datatypes: TokenStream = self
110            .items()
111            .map(|item| item.to_rust(item_ctx))
112            .collect::<Result<_>>()?;
113
114        let body = quote! {
115            #[allow(non_camel_case_types, unused)]
116            type address = #thecrate::types::Address;
117            #[allow(non_camel_case_types, unused)]
118            type u256 = #thecrate::types::U256;
119            #[allow(non_camel_case_types, unused)]
120            type vector<T> = ::std::vec::Vec<T>;
121
122            #datatypes
123        };
124
125        Ok(ModuleParts { ident, docs, body })
126    }
127}
128
129/// Context for Rust code generation from a Move item.
130#[derive(Clone, Copy)]
131pub struct ItemContext<'a> {
132    /// Path to a crate/module which exports:
133    /// - a `types` module with `Address` and `U256` types from `moverox-types`
134    /// - a `traits` module with `HasKey`, `MoveDatatype` and `MoveType` traits from `moverox-traits`
135    /// - the `serde` crate
136    /// - an `Otw` type
137    pub thecrate: &'a TokenStream,
138    /// Move package address as an `0x`-prefixed hex string.
139    pub package: Option<&'a LiteralString>,
140    /// Move module name.
141    pub module: Option<&'a Ident>,
142    /// Mapping of Move named addresses to Rust paths.
143    ///
144    /// Used to map Move datatype paths to Rust-equivalents.
145    pub address_map: &'a HashMap<Ident, TokenStream>,
146}
147
148#[sealed::sealed]
149pub trait ItemGen {
150    fn to_rust(&self, ctx: ItemContext<'_>) -> Result<TokenStream>;
151}
152
153#[sealed::sealed]
154impl ItemGen for Item {
155    fn to_rust(&self, ctx: ItemContext<'_>) -> Result<TokenStream> {
156        use move_syn::ItemKind as K;
157        let Self { attrs, kind, .. } = self;
158
159        let (docs, generated) = match kind {
160            K::Struct(s) => {
161                let err_ctx = |err| format!("struct {}: {err}", s.ident);
162                let (docs, otw_types) = crate::attributes::extract(attrs).map_err(err_ctx)?;
163                let generated = s.to_rust(otw_types, ctx).map_err(err_ctx)?;
164                (docs, generated)
165            }
166            K::Enum(e) => {
167                let err_ctx = |err| format!("enum {}: {err}", e.ident);
168                let (docs, otw_types) = crate::attributes::extract(attrs).map_err(err_ctx)?;
169                let generated = self::move_enum::to_rust(e, otw_types, ctx).map_err(err_ctx)?;
170                (docs, generated)
171            }
172            _ => return non_datatype_gen(attrs),
173        };
174
175        Ok(quote! {
176            #docs
177            #generated
178        })
179    }
180}
181
182fn non_datatype_gen(attrs: &[Attributes]) -> Result<TokenStream> {
183    if attrs.iter().flat_map(self::attributes::as_moverox).count() > 0 {
184        return Err(
185            "Move items other than enums/structs cannot be annotated with custom \
186            `moverox` attributes"
187                .into(),
188        );
189    }
190    Ok(TokenStream::new())
191}