Skip to main content

moverox_codegen/move_enum/
mod.rs

1use std::collections::{HashMap, HashSet};
2
3use move_syn::{FieldsKind, ItemPath};
4use quote::quote;
5use unsynn::{Ident, ToTokens as _, TokenStream};
6
7use crate::generics::GenericsExt;
8use crate::iter::BoxedIter as _;
9use crate::{ItemContext, Result, named_fields, positional_fields};
10
11/// The full Rust struct declaration and its `new` constructor.
12pub(super) fn to_rust(
13    this: &move_syn::Enum,
14    otw_types: HashSet<Ident>,
15    ctx: ItemContext,
16) -> Result<TokenStream> {
17    let move_syn::Enum {
18        ident, generics, ..
19    } = this;
20
21    let extra_attrs: TokenStream = ctx
22        .package
23        .into_iter()
24        .map(unsynn::ToTokens::to_token_stream)
25        .map(|addr| quote!(#[move_(address = #addr)]))
26        .chain(ctx.module.map(|ident| quote!(#[move_(module = #ident)])))
27        .collect();
28
29    let type_generics = generics
30        .as_ref()
31        .map(|g| g.type_generics(ctx.thecrate, otw_types))
32        .transpose()
33        .map(Option::unwrap_or_default)?;
34
35    let mut phantoms = unused_phantoms(this);
36    let variants = this
37        .variants()
38        // HACK: pipe unused phantom parameters into the first variant to become phantom data fields
39        .map(|var| variant_to_rust(var, &std::mem::take(&mut phantoms), ctx.address_map));
40
41    // NOTE: this has to be formatted as a string first, so that `quote!` will turn it into a
42    // string literal later, which is what `#[serde(crate = ...)]` accepts
43    let thecrate = ctx.thecrate;
44    let serde_crate = format!("{thecrate}::serde").replace(" ", "");
45    Ok(quote! {
46        #[derive(
47            Clone,
48            Debug,
49            PartialEq,
50            Eq,
51            Hash,
52            #thecrate::traits::MoveDatatype,
53            #thecrate::serde::Deserialize,
54            #thecrate::serde::Serialize,
55        )]
56        #[move_(crate = #thecrate::traits)]
57        #[serde(crate = #serde_crate)]
58        #extra_attrs
59        #[allow(non_snake_case)]
60        pub enum #ident #type_generics {
61            #(#variants),*
62        }
63    })
64}
65
66/// Collect `this` enum's phantom types that aren't used in any of its field types.
67fn unused_phantoms(this: &move_syn::Enum) -> Vec<Ident> {
68    let Some(generics) = this.generics.as_ref() else {
69        return Vec::new(); // short-circuit if the enum has no generics
70    };
71
72    let maybe_phantom_leaf_types: HashSet<_> = enum_leaf_types(this)
73        .filter_map(|path| match path {
74            ItemPath::Ident(ident) => Some(ident),
75            _ => None,
76        })
77        .collect();
78
79    generics
80        .phantoms()
81        .filter(|&ident| !maybe_phantom_leaf_types.contains(ident))
82        .cloned()
83        .collect()
84}
85
86/// Find all type parameters of `this` enum's fields that are 'leaf's, recursively.
87///
88/// A type parameter is a 'leaf' if it has no type parameters itself
89fn enum_leaf_types(this: &move_syn::Enum) -> Box<dyn Iterator<Item = &ItemPath> + '_> {
90    this.variants()
91        .flat_map(|var| &var.fields)
92        .flat_map(|fields| match fields {
93            FieldsKind::Positional(positional) => {
94                leaf_types_recursive(positional.fields().map(|field| &field.ty).boxed())
95            }
96            FieldsKind::Named(named) => {
97                leaf_types_recursive(named.fields().map(|field| &field.ty).boxed())
98            }
99        })
100        .boxed()
101}
102
103fn leaf_types_recursive<'a>(
104    types: Box<dyn Iterator<Item = &'a move_syn::Type> + 'a>,
105) -> Box<dyn Iterator<Item = &'a ItemPath> + 'a> {
106    types
107        .into_iter()
108        .flat_map(|t| {
109            t.type_args.as_ref().map_or_else(
110                || std::iter::once(&t.path).boxed(),
111                |t_args| leaf_types_recursive(t_args.types().boxed()),
112            )
113        })
114        .boxed()
115}
116
117fn variant_to_rust(
118    this: &move_syn::EnumVariant,
119    phantoms: &[Ident],
120    address_map: &HashMap<Ident, TokenStream>,
121) -> TokenStream {
122    use move_syn::FieldsKind as K;
123    let move_syn::EnumVariant {
124        attrs,
125        ident,
126        fields,
127    } = this;
128    let attrs = attrs
129        .iter()
130        .filter(|attr| attr.is_doc())
131        .map(|attr| attr.to_token_stream());
132
133    // Move enum variants can have empty fields
134    let bool_if_empty = false;
135    // Public enums in Rust already have all their variants' fields public
136    let visibility = false;
137
138    // If the variant is a unit (empty) one but there are phantom parameters, make it into a
139    // positional fields one with just the phantom data
140    let default_fields = (!phantoms.is_empty()).then(|| {
141        positional_fields::to_rust(
142            &Default::default(),
143            phantoms.iter(),
144            address_map,
145            bool_if_empty,
146            visibility,
147        )
148    });
149
150    let fields = fields
151        .as_ref()
152        .map(|kind| match kind {
153            K::Named(named) => {
154                named_fields::to_rust(named, phantoms.iter(), address_map, visibility)
155            }
156            K::Positional(positional) => positional_fields::to_rust(
157                positional,
158                phantoms.iter(),
159                address_map,
160                bool_if_empty,
161                visibility,
162            ),
163        })
164        .or(default_fields)
165        .unwrap_or_default();
166
167    quote! {
168        #(#attrs)*
169        #ident #fields
170    }
171}