From f9ced12fee4b356f6b60778b688a0f01033db4fb Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 14 Feb 2023 02:15:28 +0100 Subject: [PATCH] refactor(abigen): solidity types expansion (#2131) * refactor: event input expansion * refactor: common expand params * refactor: method params expansion * refactor: struct fields expansion * refactor: types array expansion * clippy --- .../src/contract/common.rs | 62 +----- .../src/contract/errors.rs | 10 +- .../src/contract/events.rs | 94 +-------- .../src/contract/methods.rs | 159 ++++----------- .../src/contract/structs.rs | 22 +-- .../src/contract/types.rs | 186 ++++++++++++++---- 6 files changed, 214 insertions(+), 319 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs index b53b08d6..08178908 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs @@ -1,66 +1,8 @@ -use super::{types, util, Context}; -use ethers_core::{ - abi::{Param, ParamType}, - macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}, -}; +use super::Context; +use ethers_core::macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::quote; -/// Expands to the `name : type` pairs for the params -pub(crate) fn expand_params<'a, F>( - params: &[Param], - resolve_tuple: F, -) -> eyre::Result> -where - F: Fn(&str) -> Option<&'a str>, -{ - params - .iter() - .enumerate() - .map(|(idx, param)| { - let name = util::expand_input_name(idx, ¶m.name); - let ty = expand_param_type(param, ¶m.kind, |s| resolve_tuple(s))?; - Ok((name, ty)) - }) - .collect() -} - -/// returns the Tokenstream for the corresponding rust type -pub(crate) fn expand_param_type<'a, F>( - param: &Param, - kind: &ParamType, - resolve_tuple: F, -) -> eyre::Result -where - F: Fn(&str) -> Option<&'a str>, -{ - match kind { - ParamType::Array(ty) => { - let ty = expand_param_type(param, ty, resolve_tuple)?; - Ok(quote! { - ::std::vec::Vec<#ty> - }) - } - ParamType::FixedArray(ty, size) => { - let ty = expand_param_type(param, ty, resolve_tuple)?; - let size = *size; - Ok(quote! {[#ty; #size]}) - } - ParamType::Tuple(_) => { - let ty = if let Some(rust_struct_name) = - param.internal_type.as_ref().and_then(|s| resolve_tuple(s.as_str())) - { - let ident = util::ident(rust_struct_name); - quote! {#ident} - } else { - types::expand(kind)? - }; - Ok(ty) - } - _ => types::expand(kind), - } -} - pub(crate) fn imports(name: &str) -> TokenStream { let doc_str = format!("{name} was auto-generated with ethers-rs Abigen. More information at: "); diff --git a/ethers-contract/ethers-contract-abigen/src/contract/errors.rs b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs index 4462386e..f8fea826 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/errors.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs @@ -1,7 +1,9 @@ //! derive error bindings -use super::{util, Context}; -use crate::contract::common::{expand_data_struct, expand_data_tuple, expand_params}; +use super::{ + common::{expand_data_struct, expand_data_tuple}, + types, util, Context, +}; use ethers_core::{ abi::{ethabi::AbiError, ErrorExt}, macros::{ethers_contract_crate, ethers_core_crate}, @@ -81,7 +83,9 @@ impl Context { /// Expands to the `name : type` pairs of the function's outputs fn expand_error_params(&self, error: &AbiError) -> Result> { - expand_params(&error.inputs, |s| self.internal_structs.get_struct_type(s)) + types::expand_params(&error.inputs, |p| { + p.internal_type.as_deref().and_then(|s| self.internal_structs.get_struct_type(s)) + }) } /// The name ident of the errors enum diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs index 1dce3ef7..bef73855 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs @@ -1,12 +1,12 @@ use super::{types, util, Context}; use crate::util::can_derive_defaults; use ethers_core::{ - abi::{Event, EventExt, EventParam, Param, ParamType}, + abi::{Event, EventExt, Param}, macros::{ethers_contract_crate, ethers_core_crate}, }; use eyre::Result; use inflector::Inflector; -use proc_macro2::{Ident, Literal, TokenStream}; +use proc_macro2::{Ident, TokenStream}; use quote::quote; use std::collections::BTreeMap; @@ -136,85 +136,6 @@ impl Context { } } - /// Expands an event property type. - /// - /// Note that this is slightly different from expanding a Solidity type as - /// complex types like arrays and strings get emitted as hashes when they are - /// indexed. - /// If a complex types matches with a struct previously parsed by the internal structs, - /// we can replace it - fn expand_input_type( - &self, - event: &Event, - input: &EventParam, - idx: usize, - ) -> Result { - let ethers_core = ethers_core_crate(); - Ok(match (&input.kind, input.indexed) { - (ParamType::Array(_), true) => { - quote! { #ethers_core::types::H256 } - } - (ParamType::FixedArray(_, _), true) => { - quote! { #ethers_core::types::H256 } - } - (ParamType::Tuple(..), true) => { - quote! { #ethers_core::types::H256 } - } - (ParamType::Bytes, true) | (ParamType::String, true) => { - quote! { #ethers_core::types::H256 } - } - (ParamType::Tuple(_), false) => { - let ty = if let Some(rust_struct_name) = - self.internal_structs.get_event_input_struct_type(&event.name, idx) - { - let ident = util::ident(rust_struct_name); - quote! {#ident} - } else { - types::expand(&input.kind)? - }; - ty - } - (ParamType::Array(_), _) => { - // represents an array of a struct - if let Some(rust_struct_name) = - self.internal_structs.get_event_input_struct_type(&event.name, idx) - { - let ty = util::ident(rust_struct_name); - return Ok(quote! {::std::vec::Vec<#ty>}) - } - types::expand(&input.kind)? - } - (ParamType::FixedArray(_, size), _) => { - // represents a fixed array of a struct - if let Some(rust_struct_name) = - self.internal_structs.get_event_input_struct_type(&event.name, idx) - { - let ty = util::ident(rust_struct_name); - let size = Literal::usize_unsuffixed(*size); - return Ok(quote! {[#ty; #size]}) - } - types::expand(&input.kind)? - } - (kind, _) => types::expand(kind)?, - }) - } - - /// Expands the name-type pairs for the given inputs - fn expand_event_params(&self, event: &Event) -> Result> { - event - .inputs - .iter() - .enumerate() - .map(|(idx, input)| { - // NOTE: Events can contain nameless values. - let name = util::expand_input_name(idx, &input.name); - let ty = self.expand_input_type(event, input, idx)?; - - Ok((name, ty, input.indexed)) - }) - .collect() - } - /// Expands into a single method for contracting an event stream. fn expand_filter(&self, event: &Event) -> TokenStream { let name = &event.name; @@ -251,7 +172,7 @@ impl Context { let event_name = event_struct_name(&event.name, sig); - let params = self.expand_event_params(event)?; + let params = types::expand_event_inputs(event, &self.internal_structs)?; // expand as a tuple if all fields are anonymous let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty()); let data_type_definition = if all_anonymous_fields { @@ -355,6 +276,7 @@ mod tests { use super::*; use crate::Abigen; use ethers_core::abi::{EventParam, Hash, ParamType}; + use proc_macro2::Literal; /// Expands a 256-bit `Hash` into a literal representation that can be used with /// quasi-quoting for code generation. We do this to avoid allocating at runtime @@ -445,7 +367,7 @@ mod tests { }; let cx = test_context(); - let params = cx.expand_event_params(&event).unwrap(); + let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let name = event_struct_name(&event.name, None); let definition = expand_data_struct(&name, ¶ms); @@ -469,7 +391,7 @@ mod tests { }; let cx = test_context_with_alias("Foo(bool,address)", "FooAliased"); - let params = cx.expand_event_params(&event).unwrap(); + let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let alias = Some(util::ident("FooAliased")); let name = event_struct_name(&event.name, alias); let definition = expand_data_struct(&name, ¶ms); @@ -494,7 +416,7 @@ mod tests { }; let cx = test_context(); - let params = cx.expand_event_params(&event).unwrap(); + let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let name = event_struct_name(&event.name, None); let definition = expand_data_tuple(&name, ¶ms); @@ -515,7 +437,7 @@ mod tests { }; let cx = test_context_with_alias("Foo(bool,address)", "FooAliased"); - let params = cx.expand_event_params(&event).unwrap(); + let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let alias = Some(util::ident("FooAliased")); let name = event_struct_name(&event.name, alias); let definition = expand_data_tuple(&name, ¶ms); diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index b4d1ed21..0df43602 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -1,10 +1,8 @@ -use std::collections::{btree_map::Entry, BTreeMap, HashMap, HashSet}; - -use super::{types, util, Context}; -use crate::{ - contract::common::{expand_data_struct, expand_data_tuple, expand_param_type, expand_params}, - util::can_derive_defaults, +use super::{ + common::{expand_data_struct, expand_data_tuple}, + types, Context, }; +use crate::util::{self, can_derive_defaults}; use ethers_core::{ abi::{Function, FunctionExt, Param, ParamType}, macros::{ethers_contract_crate, ethers_core_crate}, @@ -14,6 +12,7 @@ use eyre::{Context as _, Result}; use inflector::Inflector; use proc_macro2::{Literal, TokenStream}; use quote::quote; +use std::collections::{btree_map::Entry, BTreeMap, HashMap, HashSet}; use syn::Ident; /// The maximum amount of overloaded functions that are attempted to auto aliased with their param @@ -304,127 +303,45 @@ impl Context { /// Expands to the `name : type` pairs of the function's inputs fn expand_input_params(&self, fun: &Function) -> Result> { - fun.inputs - .iter() - .enumerate() - .map(|(idx, param)| { - let name = util::expand_input_name(idx, ¶m.name); - let ty = self.expand_input_param_type(fun, ¶m.name, ¶m.kind)?; - Ok((name, ty)) - }) - .collect() - } - - /// Expands to the `name : type` pairs of the function's outputs - fn expand_output_params(&self, fun: &Function) -> Result> { - expand_params(&fun.outputs, |s| { - self.internal_structs.get_function_output_struct_type(&fun.name, s) + types::expand_params(&fun.inputs, |p| { + self.internal_structs.get_function_input_struct_type(&fun.name, &p.name) }) } - /// Expands to the return type of a function - fn expand_outputs(&self, fun: &Function) -> Result { - let mut outputs = Vec::with_capacity(fun.outputs.len()); - for param in fun.outputs.iter() { - let ty = self.expand_output_param_type(fun, param, ¶m.kind)?; - outputs.push(ty); - } - - let return_ty = match outputs.len() { - 0 => quote! { () }, - 1 => outputs[0].clone(), - _ => { - quote! { (#( #outputs ),*) } - } - }; - Ok(return_ty) + /// Expands to the `name: type` pairs of the function's outputs + fn expand_output_params(&self, fun: &Function) -> Result> { + types::expand_params(&fun.outputs, |p| { + p.internal_type + .as_deref() + .and_then(|s| self.internal_structs.get_function_output_struct_type(&fun.name, s)) + }) } /// Expands the arguments for the call that eventually calls the contract - fn expand_contract_call_args(&self, fun: &Function) -> Result { - let mut call_args = Vec::with_capacity(fun.inputs.len()); - for (idx, param) in fun.inputs.iter().enumerate() { + fn expand_contract_call_args(&self, fun: &Function) -> TokenStream { + let mut call_args = fun.inputs.iter().enumerate().map(|(idx, param)| { let name = util::expand_input_name(idx, ¶m.name); - let call_arg = match param.kind { + match param.kind { // this is awkward edge case where the function inputs are a single struct - // we need to force this argument into a tuple so it gets expanded to `((#name,))` - // this is currently necessary because internally `flatten_tokens` is called which - // removes the outermost `tuple` level and since `((#name))` is not - // a rust tuple it doesn't get wrapped into another tuple that will be peeled off by - // `flatten_tokens` + // we need to force this argument into a tuple so it gets expanded to + // `((#name,))` this is currently necessary because + // internally `flatten_tokens` is called which removes the + // outermost `tuple` level and since `((#name))` is not + // a rust tuple it doesn't get wrapped into another tuple that will be peeled + // off by `flatten_tokens` ParamType::Tuple(_) if fun.inputs.len() == 1 => { // make sure the tuple gets converted to `Token::Tuple` - quote! {(#name,)} + quote!((#name,)) } _ => name, - }; - call_args.push(call_arg); + } + }); + + match fun.inputs.len() { + 0 => quote!(()), + 1 => call_args.next().unwrap(), + _ => quote!(( #( #call_args ),* )), } - let call_args = match call_args.len() { - 0 => quote! { () }, - 1 => quote! { #( #call_args )* }, - _ => quote! { ( #(#call_args, )* ) }, - }; - - Ok(call_args) - } - - /// returns the Tokenstream for the corresponding rust type of the param - fn expand_input_param_type( - &self, - fun: &Function, - param: &str, - kind: &ParamType, - ) -> Result { - let ethers_core = ethers_core_crate(); - match kind { - ParamType::Array(ty) => { - let ty = self.expand_input_param_type(fun, param, ty)?; - Ok(quote! { - ::std::vec::Vec<#ty> - }) - } - ParamType::FixedArray(ty, size) => { - let ty = match **ty { - ParamType::Uint(size) => { - if size / 8 == 1 { - // this prevents type ambiguity with `FixedBytes` - quote! { #ethers_core::types::Uint8} - } else { - self.expand_input_param_type(fun, param, ty)? - } - } - _ => self.expand_input_param_type(fun, param, ty)?, - }; - - let size = *size; - Ok(quote! {[#ty; #size]}) - } - ParamType::Tuple(_) => { - let ty = if let Some(rust_struct_name) = - self.internal_structs.get_function_input_struct_type(&fun.name, param) - { - let ident = util::ident(rust_struct_name); - quote! {#ident} - } else { - types::expand(kind)? - }; - Ok(ty) - } - _ => types::expand(kind), - } - } - - /// returns the TokenStream for the corresponding rust type of the output param - fn expand_output_param_type( - &self, - fun: &Function, - param: &Param, - kind: &ParamType, - ) -> Result { - expand_param_type(param, kind, |s| { - self.internal_structs.get_function_output_struct_type(&fun.name, s) - }) } /// Expands a single function with the given alias @@ -439,12 +356,22 @@ impl Context { let selector_tokens = expand_selector(selector); - let contract_args = self.expand_contract_call_args(function)?; + let contract_args = self.expand_contract_call_args(function); let function_params = self.expand_input_params(function)?.into_iter().map(|(name, ty)| quote! { #name: #ty }); let function_params = quote! { #( , #function_params )* }; - let outputs = self.expand_outputs(function)?; + let outputs = { + let mut out = self.expand_output_params(function)?; + match out.len() { + 0 => quote!(()), + 1 => out.pop().unwrap().1, + _ => { + let iter = out.into_iter().map(|(_, ty)| ty); + quote!(( #( #iter ),* )) + } + } + }; let doc_str = format!("Calls the contract's `{name}` (0x{}) function", hex::encode(selector)); diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs index b2b3dbeb..13dcac4f 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs @@ -93,7 +93,7 @@ impl Context { for field in sol_struct.fields() { let ty = match field.r#type() { FieldType::Elementary(ty) => types::expand(ty)?, - FieldType::Struct(struct_ty) => expand_struct_type(struct_ty), + FieldType::Struct(struct_ty) => types::expand_struct_type(struct_ty), FieldType::Mapping(_) => { eyre::bail!("Mapping types in struct `{}` are not supported {:?}", name, field) } @@ -154,7 +154,7 @@ impl Context { fields.push(quote! { pub #field_name: #ty }); } FieldType::Struct(struct_ty) => { - let ty = expand_struct_type(struct_ty); + let ty = types::expand_struct_type(struct_ty); fields.push(quote! { pub #field_name: #ty }); let name = struct_ty.name(); @@ -592,24 +592,6 @@ fn struct_type_projections(name: &str) -> Vec { iter.rev().map(str::to_string).collect() } -/// Expands to the rust struct type -fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream { - match struct_ty { - StructFieldType::Type(ty) => { - let ty = util::ident(&ty.name().to_pascal_case()); - quote! {#ty} - } - StructFieldType::Array(ty) => { - let ty = expand_struct_type(ty); - quote! {::std::vec::Vec<#ty>} - } - StructFieldType::FixedArray(ty, size) => { - let ty = expand_struct_type(ty); - quote! { [#ty; #size]} - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/ethers-contract/ethers-contract-abigen/src/contract/types.rs b/ethers-contract/ethers-contract-abigen/src/contract/types.rs index 609add65..f1918d70 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/types.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/types.rs @@ -1,9 +1,17 @@ -use ethers_core::{abi::ParamType, macros::ethers_core_crate}; -use eyre::{bail, Result}; -use proc_macro2::{Literal, TokenStream}; -use quote::quote; +//! Types expansion -pub(crate) fn expand(kind: &ParamType) -> Result { +use crate::{util, InternalStructs}; +use ethers_core::{ + abi::{struct_def::StructFieldType, Event, EventParam, Param, ParamType}, + macros::ethers_core_crate, +}; +use eyre::{bail, Result}; +use inflector::Inflector; +use proc_macro2::{Literal, TokenStream}; +use quote::{quote, ToTokens}; + +/// Expands a ParamType Solidity type to its Rust equivalent. +pub fn expand(kind: &ParamType) -> Result { let ethers_core = ethers_core_crate(); match kind { @@ -16,7 +24,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result { 5..=8 => Ok(quote! { i64 }), 9..=16 => Ok(quote! { i128 }), 17..=32 => Ok(quote! { #ethers_core::types::I256 }), - _ => bail!("unsupported solidity type int{}", n), + _ => bail!("unsupported solidity type int{n}"), }, ParamType::Uint(n) => match n / 8 { 1 => Ok(quote! { u8 }), @@ -25,41 +33,151 @@ pub(crate) fn expand(kind: &ParamType) -> Result { 5..=8 => Ok(quote! { u64 }), 9..=16 => Ok(quote! { u128 }), 17..=32 => Ok(quote! { #ethers_core::types::U256 }), - _ => bail!("unsupported solidity type uint{}", n), + _ => bail!("unsupported solidity type uint{n}"), }, - ParamType::Bool => Ok(quote! { bool }), - ParamType::String => Ok(quote! { String }), - ParamType::Array(t) => { - let inner = expand(t)?; - Ok(quote! { Vec<#inner> }) - } - ParamType::FixedBytes(n) => { - // TODO(nlordell): what is the performance impact of returning large - // `FixedBytes` and `FixedArray`s with `web3`? - let size = Literal::usize_unsuffixed(*n); - Ok(quote! { [u8; #size] }) - } - ParamType::FixedArray(t, n) => { - // TODO(nlordell): see above - let inner = match **t { - ParamType::Uint(size) => { - if size / 8 == 1 { - // this prevents type ambiguity with `FixedBytes` - quote! { #ethers_core::types::Uint8} - } else { - expand(t)? - } - } - _ => expand(t)?, + ParamType::Bool => Ok(quote!(bool)), + ParamType::String => Ok(quote!(::std::string::String)), + ParamType::Array(ty) => Ok(array(expand(ty)?, None)), + ParamType::FixedBytes(n) => Ok(array(quote!(u8), Some(*n))), + ParamType::FixedArray(ty, n) => { + let ty = match **ty { + // this prevents type ambiguity with `FixedBytes` + // see: https://github.com/gakonst/ethers-rs/issues/1636 + ParamType::Uint(size) if size / 8 == 1 => quote!(#ethers_core::types::Uint8), + _ => expand(ty)?, }; - let size = Literal::usize_unsuffixed(*n); - Ok(quote! { [#inner; #size] }) + Ok(array(ty, Some(*n))) } ParamType::Tuple(members) => { eyre::ensure!(!members.is_empty(), "Tuple must have at least 1 member"); let members = members.iter().map(expand).collect::, _>>()?; - Ok(quote! { (#(#members,)*) }) + Ok(quote!(( #( #members ),* ))) } } } + +/// Expands the event's inputs. +pub fn expand_event_inputs( + event: &Event, + internal_structs: &InternalStructs, +) -> Result> { + event + .inputs + .iter() + .enumerate() + .map(|(index, input)| { + // NOTE: Events can contain nameless values. + expand_event_input(input, &event.name, index, internal_structs) + .map(|ty| (util::expand_input_name(index, &input.name), ty, input.indexed)) + }) + .collect() +} + +/// Expands an event property type. +/// +/// Note that this is slightly different from expanding a Solidity type as complex types like arrays +/// and strings get emitted as hashes when they are indexed. +/// +/// If a complex types matches with a struct previously parsed by the internal structs, we can +/// replace it. +fn expand_event_input( + input: &EventParam, + name: &str, + index: usize, + internal_structs: &InternalStructs, +) -> Result { + let kind = &input.kind; + match (kind, input.indexed) { + (ParamType::Array(_), true) | + (ParamType::FixedArray(_, _), true) | + (ParamType::Tuple(_), true) | + (ParamType::Bytes, true) | + (ParamType::String, true) => { + let ethers_core = ethers_core_crate(); + Ok(quote!(#ethers_core::types::H256)) + } + + (ParamType::Array(_), false) | + (ParamType::FixedArray(_, _), false) | + (ParamType::Tuple(_), false) => { + match internal_structs.get_event_input_struct_type(name, index) { + Some(ty) => { + let ty = util::ident(ty); + match kind { + ParamType::Array(_) => Ok(array(ty, None)), + ParamType::FixedArray(_, size) => Ok(array(ty, Some(*size))), + ParamType::Tuple(_) => Ok(quote!(#ty)), + _ => unreachable!(), + } + } + None => expand(kind), + } + } + + _ => expand(kind), + } +} + +/// Expands `params` to `(name, type)` tokens pairs, while resolving tuples' types using the given +/// function. +pub fn expand_params<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>( + params: &'a [Param], + resolve_tuple: F, +) -> Result> { + params + .iter() + .enumerate() + .map(|(idx, param)| { + // NOTE: Params can be unnamed. + expand_resolved(¶m.kind, param, &resolve_tuple) + .map(|ty| (util::expand_input_name(idx, ¶m.name), ty)) + }) + .collect() +} + +/// Expands a ParamType Solidity type to its Rust equivalent, while resolving tuples' types using +/// the given function. +fn expand_resolved<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>( + kind: &'a ParamType, + param: &'a Param, + resolve_tuple: &F, +) -> Result { + match kind { + ParamType::Array(ty) => Ok(array(expand_resolved(ty, param, resolve_tuple)?, None)), + ParamType::FixedArray(ty, size) => { + Ok(array(expand_resolved(ty, param, resolve_tuple)?, Some(*size))) + } + ParamType::Tuple(_) => match resolve_tuple(param) { + Some(ty) => { + let ty = util::ident(ty); + Ok(quote!(#ty)) + } + None => expand(kind), + }, + _ => expand(kind), + } +} + +/// Expands to the Rust struct type. +pub fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream { + match struct_ty { + StructFieldType::Type(ty) => { + let ty = util::ident(&ty.name().to_pascal_case()); + quote!(#ty) + } + StructFieldType::Array(ty) => array(expand_struct_type(ty), None), + StructFieldType::FixedArray(ty, size) => array(expand_struct_type(ty), Some(*size)), + } +} + +/// Expands `ty` into a Rust array or vector. +fn array(ty: T, size: Option) -> TokenStream { + match size { + Some(size) => { + let size = Literal::usize_unsuffixed(size); + quote!([#ty; #size]) + } + None => quote!(::std::vec::Vec<#ty>), + } +}