moverox_codegen/
named_fields.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use quote::quote;
5use unsynn::{Ident, ToTokens as _, TokenStream};
6
7use crate::move_type;
8
9/// Move named field converted to Rust.
10#[derive(Clone)]
11pub(super) struct Rust<'a> {
12    pub(super) attrs: TokenStream,
13    pub(super) ident: Cow<'a, Ident>,
14    pub(super) ty: TokenStream,
15}
16
17/// `{ name: T, .. }` in Move to Rust.
18///
19/// `visibility` controls whether a `pub` visibility modifier is added to the field.
20pub(super) fn to_rust<'a>(
21    this: &'a move_syn::NamedFields,
22    phantoms: impl Iterator<Item = &'a Ident>,
23    address_map: &HashMap<Ident, TokenStream>,
24    visibility: bool,
25) -> TokenStream {
26    let mut move_fields = to_rust_fields(this, address_map);
27
28    let phantom_data = phantoms.map(|ty| {
29        let field = Ident::new(&format!("_{ty}"), ty.span());
30        quote! {
31            #[serde(skip)]
32            #field: ::std::marker::PhantomData<#ty>
33        }
34    });
35
36    let rs_fields: &mut dyn Iterator<Item = TokenStream> = if let Some(field) = move_fields.next() {
37        &mut std::iter::once(field)
38            .chain(move_fields)
39            .map(|Rust { attrs, ident, ty }| {
40                #[expect(clippy::obfuscated_if_else)]
41                let vis = visibility.then(|| quote!(pub)).unwrap_or_default();
42                quote! {
43                    #attrs
44                    #vis #ident: #ty
45                }
46            })
47            .chain(phantom_data)
48    } else {
49        &mut std::iter::once(quote! {
50            /// BCS for empty structs actually encodes a single boolean hidden field
51            dummy_field: bool
52        })
53        .chain(phantom_data)
54    };
55
56    quote! {
57        { #(#rs_fields),* }
58    }
59}
60
61pub(super) fn to_rust_fields<'a>(
62    this: &'a move_syn::NamedFields,
63    address_map: &HashMap<Ident, TokenStream>,
64) -> impl Iterator<Item = Rust<'a>> + Clone {
65    this.fields().map(
66        |move_syn::NamedField {
67             attrs, ident, ty, ..
68         }| {
69            Rust {
70                attrs: attrs
71                    .iter()
72                    .filter(|attr| attr.is_doc())
73                    .map(|attr| attr.to_token_stream())
74                    .collect(),
75                ident: sanitize_ident(ident),
76                ty: move_type::to_rust_with_substitutions(ty, address_map),
77            }
78        },
79    )
80}
81
82fn sanitize_ident(ident: &Ident) -> Cow<'_, Ident> {
83    let ident_str = ident.to_string();
84    // https://doc.rust-lang.org/reference/keywords.html
85    match ident_str.as_str() {
86        "abstract" | "become" | "box" | "do" | "final" | "macro" | "override" | "priv"
87        | "typeof" | "unsized" | "virtual" | "yield" | "try" | "gen" | "as" | "break" | "const"
88        | "continue" | "crate" | "else" | "enum" | "extern" | "false" | "fn" | "for" | "if"
89        | "impl" | "in" | "let" | "loop" | "match" | "mod" | "move" | "mut" | "pub" | "ref"
90        | "return" | "self" | "Self" | "static" | "struct" | "super" | "trait" | "true"
91        | "type" | "unsafe" | "use" | "where" | "while" | "async" | "await" | "dyn" => {
92            Cow::Owned(Ident::new_raw(&ident_str, ident.span()))
93        }
94        _ => Cow::Borrowed(ident),
95    }
96}