use std::collections::{HashMap, HashSet};
use move_syn::ItemPath;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
mod braced;
mod tuple;
use braced::BracedStructExt as _;
use tuple::TupleStructExt as _;
use crate::generics::GenericsExt;
use crate::iter::BoxedIter as _;
use crate::{ItemContext, Result};
pub(super) trait StructGen {
fn to_rust(&self, otw_types: HashSet<Ident>, ctx: ItemContext<'_>) -> Result<TokenStream>;
}
impl StructGen for move_syn::Struct {
fn to_rust(&self, otw_types: HashSet<Ident>, ctx: ItemContext<'_>) -> Result<TokenStream> {
let decl = self.rust_declaration(otw_types, ctx)?;
let impl_new = self.impl_new(ctx.address_map);
let impl_has_key_maybe = self.impl_has_key(ctx.thecrate).unwrap_or_default();
Ok(quote! {
#decl
#impl_new
#impl_has_key_maybe
})
}
}
trait StructExt {
fn rust_declaration(
&self,
otw_types: HashSet<Ident>,
ctx: ItemContext<'_>,
) -> Result<TokenStream>;
fn impl_new(&self, address_map: &HashMap<Ident, TokenStream>) -> TokenStream;
fn impl_has_key(&self, thecrate: &TokenStream) -> Option<TokenStream>;
fn extra_derives(&self) -> Option<TokenStream>;
fn generics(&self) -> TokenStream;
fn type_generics(
&self,
thecrate: &TokenStream,
otw_types: HashSet<Ident>,
) -> Result<TokenStream>;
fn unused_phantoms(&self) -> impl Iterator<Item = &Ident>;
}
impl StructExt for move_syn::Struct {
fn rust_declaration(
&self,
otw_types: HashSet<Ident>,
ctx: ItemContext<'_>,
) -> Result<TokenStream> {
use move_syn::StructKind as K;
let Self { ident, kind, .. } = self;
let extra_attrs: TokenStream = ctx
.package
.into_iter()
.map(unsynn::ToTokens::to_token_stream)
.map(|addr| quote!(#[move_(address = #addr)]))
.chain(ctx.module.map(|ident| quote!(#[move_(module = #ident)])))
.collect();
let extra_derives = self.extra_derives().unwrap_or_default();
let type_generics = self.type_generics(ctx.thecrate, otw_types)?;
let contents = match kind {
K::Braced(braced) => braced.to_rust_contents(self.unused_phantoms(), ctx.address_map),
K::Tuple(tuple) => tuple.to_rust_contents(self.unused_phantoms(), ctx.address_map),
};
let thecrate = ctx.thecrate;
let serde_crate = format!("{thecrate}::serde").replace(" ", "");
Ok(quote! {
#[derive(
#extra_derives
Clone,
Debug,
PartialEq,
Eq,
Hash,
#thecrate::traits::MoveDatatype,
#thecrate::serde::Deserialize,
#thecrate::serde::Serialize,
)]
#[move_(crate = #thecrate::traits)]
#[serde(crate = #serde_crate)]
#extra_attrs
#[allow(non_snake_case)]
pub struct #ident #type_generics #contents
})
}
fn impl_new(&self, address_map: &HashMap<Ident, TokenStream>) -> TokenStream {
use move_syn::StructKind;
let Self { ident, kind, .. } = self;
let generics = self.generics();
let (args, assignments) = match kind {
StructKind::Braced(braced) => braced.impl_new(self.unused_phantoms(), address_map),
StructKind::Tuple(tuple) => tuple.impl_new(self.unused_phantoms(), address_map),
};
quote! {
impl #generics #ident #generics {
#[allow(clippy::just_underscores_and_digits, clippy::too_many_arguments)]
pub const fn new(#args) -> Self {
Self #assignments
}
}
}
}
fn impl_has_key(&self, thecrate: &TokenStream) -> Option<TokenStream> {
use move_syn::Ability;
if !self.abilities().any(|a| matches!(a, Ability::Key(_))) {
return None;
}
let ident = &self.ident;
let generics = self.generics();
Some(quote! {
impl #generics #thecrate::traits::HasKey for #ident #generics {
fn address(&self) -> #thecrate::types::Address {
self.id.id.bytes
}
}
})
}
fn extra_derives(&self) -> Option<TokenStream> {
use move_syn::StructKind;
let is_empty = match &self.kind {
StructKind::Braced(braced) => braced.is_empty(),
StructKind::Tuple(tuple) => tuple.is_empty(),
};
is_empty.then_some(quote!(Default,))
}
fn generics(&self) -> TokenStream {
self.generics
.as_ref()
.map(GenericsExt::to_rust)
.unwrap_or_default()
}
fn type_generics(
&self,
thecrate: &TokenStream,
otw_types: HashSet<Ident>,
) -> Result<TokenStream> {
self.generics
.as_ref()
.map(|g| g.type_generics(thecrate, otw_types))
.transpose()
.map(Option::unwrap_or_default)
}
fn unused_phantoms(&self) -> impl Iterator<Item = &Ident> {
unused_phantoms(self)
}
}
fn unused_phantoms(this: &move_syn::Struct) -> impl Iterator<Item = &Ident> {
let maybe_phantom_leaf_types: HashSet<_> = struct_leaf_types(this)
.filter_map(|path| match path {
ItemPath::Ident(ident) => Some(ident),
_ => None,
})
.collect();
this.generics
.iter()
.flat_map(|g| g.phantoms())
.filter(move |&ident| !maybe_phantom_leaf_types.contains(ident))
}
fn struct_leaf_types(this: &move_syn::Struct) -> Box<dyn Iterator<Item = &ItemPath> + '_> {
match &this.kind {
move_syn::StructKind::Braced(named) => {
leaf_types_recursive(named.fields().map(|field| &field.ty).boxed())
}
move_syn::StructKind::Tuple(positional) => {
leaf_types_recursive(positional.fields().map(|field| &field.ty).boxed())
}
}
.boxed()
}
fn leaf_types_recursive<'a>(
types: Box<dyn Iterator<Item = &'a move_syn::Type> + 'a>,
) -> Box<dyn Iterator<Item = &'a ItemPath> + 'a> {
types
.into_iter()
.flat_map(|t| {
t.type_args.as_ref().map_or_else(
|| std::iter::once(&t.path).boxed(),
|t_args| leaf_types_recursive(t_args.types().boxed()),
)
})
.boxed()
}