From 1aa49d3359e67b61c68a2b3bfdd458338fa778f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Thu, 23 Nov 2023 15:10:39 +0100 Subject: [PATCH 1/6] wip --- examples2/src/erc20.rs | 12 +- odra-macros/src/ast/host_ref_item.rs | 2 +- odra-macros/src/ast/mod.rs | 1 + odra-macros/src/ast/module_item.rs | 189 +++++++++++++++++++++++++++ odra-macros/src/ast/test_parts.rs | 2 +- odra-macros/src/ir/mod.rs | 100 ++++++++++++-- odra-macros/src/lib.rs | 2 +- odra-macros/src/test_utils.rs | 31 ++++- odra-macros/src/utils/expr.rs | 25 ++++ odra-macros/src/utils/syn.rs | 87 ++++++++++-- odra-macros/src/utils/ty.rs | 30 +++++ 11 files changed, 444 insertions(+), 37 deletions(-) create mode 100644 odra-macros/src/ast/module_item.rs diff --git a/examples2/src/erc20.rs b/examples2/src/erc20.rs index 41b44fc4..9cc64f7f 100644 --- a/examples2/src/erc20.rs +++ b/examples2/src/erc20.rs @@ -141,12 +141,12 @@ impl Erc20 { // autogenerated for general purpose module. mod __erc20_module { use super::Erc20; - use odra::{module::Module, prelude::*, ContractEnv, Mapping, Variable}; + use odra::prelude::*; - impl Module for Erc20 { - fn new(env: Rc) -> Self { - let total_supply = Variable::new(Rc::clone(&env), 1); - let balances = Mapping::new(Rc::clone(&env), 2); + impl odra::module::Module for Erc20 { + fn new(env: Rc) -> Self { + let total_supply = odra::Variable::new(Rc::clone(&env), 1); + let balances = odra::Mapping::new(Rc::clone(&env), 2); Self { env, total_supply, @@ -154,7 +154,7 @@ mod __erc20_module { } } - fn env(&self) -> Rc { + fn env(&self) -> Rc { self.env.clone() } } diff --git a/odra-macros/src/ast/host_ref_item.rs b/odra-macros/src/ast/host_ref_item.rs index f818117d..dfcdd1bd 100644 --- a/odra-macros/src/ast/host_ref_item.rs +++ b/odra-macros/src/ast/host_ref_item.rs @@ -198,7 +198,7 @@ mod ref_item_tests { self.env.last_call().contract_last_call(self.address) } - pub fn try_total_supply(&self) -> Result { + pub fn try_total_supply(&self) -> Result { self.env.call_contract( self.address, odra::CallDef::new( diff --git a/odra-macros/src/ast/mod.rs b/odra-macros/src/ast/mod.rs index d396ae5d..39b40a5c 100644 --- a/odra-macros/src/ast/mod.rs +++ b/odra-macros/src/ast/mod.rs @@ -1,6 +1,7 @@ mod deployer_item; mod deployer_utils; mod host_ref_item; +mod module_item; mod parts_utils; mod ref_item; mod ref_utils; diff --git a/odra-macros/src/ast/module_item.rs b/odra-macros/src/ast/module_item.rs new file mode 100644 index 00000000..decf77fb --- /dev/null +++ b/odra-macros/src/ast/module_item.rs @@ -0,0 +1,189 @@ +use quote::{TokenStreamExt, ToTokens}; +use syn::parse_quote; + +use crate::{ir::{StructIR, EnumeratedTypedField}, utils}; + +use super::parts_utils::UseSuperItem; + +#[derive(syn_derive::ToTokens)] +struct ModuleModItem { + mod_token: syn::token::Mod, + mod_ident: syn::Ident, + #[syn(braced)] + braces: syn::token::Brace, + #[syn(in = braces)] + use_super: UseSuperItem, + #[syn(in = braces)] + item: ModuleImplItem +} + +impl TryFrom<&'_ StructIR> for ModuleModItem { + type Error = syn::Error; + + fn try_from(ir: &'_ StructIR) -> Result { + Ok(Self { + mod_token: Default::default(), + mod_ident: ir.module_mod_ident(), + use_super: UseSuperItem, + braces: Default::default(), + item: ir.try_into()? + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct ModuleImplItem { + impl_token: syn::token::Impl, + trait_path: syn::Type, + for_token: syn::token::For, + module_path: syn::Ident, + #[syn(braced)] + braces: syn::token::Brace, + #[syn(in = braces)] + new_fn: NewModuleFnItem, + #[syn(in = braces)] + env_fn: EnvFnItem +} + +impl TryFrom<&'_ StructIR> for ModuleImplItem { + type Error = syn::Error; + + fn try_from(ir: &'_ StructIR) -> Result { + Ok(Self { + impl_token: Default::default(), + trait_path: utils::ty::module(), + for_token: Default::default(), + module_path: ir.module_ident(), + braces: Default::default(), + new_fn: ir.try_into()?, + env_fn: EnvFnItem + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct NewModuleFnItem { + sig: syn::Signature, + #[syn(braced)] + braces: syn::token::Brace, + #[syn(in = braces)] + #[to_tokens(|tokens, val| tokens.append_all(val))] + fields: Vec, + #[syn(in = braces)] + instance: ModuleInstanceItem +} + +impl TryFrom<&'_ StructIR> for NewModuleFnItem { + type Error = syn::Error; + + fn try_from(ir: &'_ StructIR) -> Result { + let ty_contract_env = utils::ty::contract_env(); + let env = utils::ident::env(); + let fields = ir.typed_fields()?; + Ok(Self { + sig: parse_quote!(fn new(#env: Rc<#ty_contract_env>) -> Self), + braces: Default::default(), + fields: fields.iter().map(Into::into).collect(), + instance: ir.try_into()? + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct ModuleFieldItem { + let_token: syn::token::Let, + ident: syn::Ident, + assign_token: syn::token::Eq, + field_expr: syn::Expr, + semi_token: syn::token::Semi +} + +impl From<&'_ EnumeratedTypedField> for ModuleFieldItem { + fn from(field: &'_ EnumeratedTypedField) -> Self { + Self { + let_token: Default::default(), + ident: field.ident.clone(), + assign_token: Default::default(), + field_expr: utils::expr::new_type(&field.ty, &utils::ident::env(), field.idx), + semi_token: Default::default() + } + } +} + +#[derive(syn_derive::ToTokens)] +struct ModuleInstanceItem { + self_token: syn::token::SelfType, + #[syn(braced)] + braces: syn::token::Brace, + #[syn(in = braces)] + values: syn::punctuated::Punctuated, +} + +impl TryFrom<&'_ StructIR> for ModuleInstanceItem { + type Error = syn::Error; + + fn try_from(ir: &'_ StructIR) -> Result { + Ok(Self { + self_token: Default::default(), + braces: Default::default(), + values: ir.field_names()?.into_iter().collect() + }) + } +} + +struct EnvFnItem; + +impl ToTokens for EnvFnItem { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let ty_contract_env = utils::ty::contract_env(); + let m_env = utils::member::env(); + + tokens.extend(quote::quote!( + fn env(&self) -> Rc<#ty_contract_env> { + #m_env.clone() + } + )) + } +} + +#[cfg(test)] +mod test { + use crate::test_utils; + use quote::quote; + + use super::ModuleModItem; + + #[test] + fn counter_pack() { + let module = test_utils::mock_module_definition(); + let expected = quote!( + mod __counter_pack_module { + use super::*; + + impl odra::module::Module for CounterPack { + fn new(env: Rc) -> Self { + let counter0 = ModuleWrapper::new(Rc::clone(&env), 0u8); + let counter1 = ModuleWrapper::new(Rc::clone(&env), 1u8); + let counter2 = ModuleWrapper::new(Rc::clone(&env), 2u8); + let counters = Variable::new(Rc::clone(&env), 3u8); + let counters_map = Mapping::new(Rc::clone(&env), 4u8); + Self { + env, + counter0, + counter1, + counter2, + counters, + counters_map + } + } + + fn env(&self) -> Rc { + self.env.clone() + } + } + } + ); + let actual = ModuleModItem::try_from(&module).unwrap(); + test_utils::assert_eq(actual, expected); + } +} diff --git a/odra-macros/src/ast/test_parts.rs b/odra-macros/src/ast/test_parts.rs index 884dd4ff..11715807 100644 --- a/odra-macros/src/ast/test_parts.rs +++ b/odra-macros/src/ast/test_parts.rs @@ -117,7 +117,7 @@ mod test { self.env.last_call().contract_last_call(self.address) } - pub fn try_total_supply(&self) -> Result { + pub fn try_total_supply(&self) -> Result { self.env.call_contract( self.address, odra::CallDef::new( diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index 4367cbd6..5f5cfa78 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -1,26 +1,100 @@ -use crate::utils; -use proc_macro2::{Ident, TokenStream}; -use quote::format_ident; -use syn::{parse_quote, ItemImpl}; +use crate::{utils, test_utils}; +use proc_macro2::Ident; +use quote::{format_ident, ToTokens}; +use syn::{parse_quote, spanned::Spanned}; const CONSTRUCTOR_NAME: &str = "init"; -pub struct ModuleIR { - code: ItemImpl +macro_rules! try_parse { + ($from:path => $to:ident) => { + pub struct $to { + code: $from + } + + impl TryFrom<&proc_macro2::TokenStream> for $to { + type Error = syn::Error; + + fn try_from(stream: &proc_macro2::TokenStream) -> Result { + Ok(Self { + code: syn::parse2::<$from>(stream.clone())? + }) + } + } + }; } -impl TryFrom<&TokenStream> for ModuleIR { - type Error = syn::Error; +try_parse!(syn::ItemStruct => StructIR); + +impl StructIR { + pub fn field_names(&self) -> Result, syn::Error> { + utils::syn::struct_fields_ident(&self.code) + } + + pub fn module_ident(&self) -> syn::Ident { + utils::syn::ident_from_struct(&self.code) + } + + pub fn module_str(&self) -> String { + self.module_ident().to_string() + } + + pub fn module_mod_ident(&self) -> syn::Ident { + format_ident!( + "__{}_module", + odra_utils::camel_to_snake(self.module_ident()) + ) + } + + pub fn typed_fields(&self) -> Result, syn::Error> { + let fields = utils::syn::struct_fields(&self.code)?; + for (_, ty) in &fields { + Self::validate_ty(ty)?; + } - fn try_from(value: &TokenStream) -> Result { - Ok(Self { - code: syn::parse2::(value.clone())? - }) + fields + .into_iter() + .enumerate() + .map(|(idx, (ident, ty))| Ok(EnumeratedTypedField { + idx: idx as u8, + ident, + ty: utils::syn::clear_generics(&ty)?, + })) + .collect() + } + + fn validate_ty(ty: &syn::Type) -> Result<(), syn::Error> { + let non_generic_ty = utils::syn::clear_generics(ty)?; + + let valid_types = vec![ + utils::ty::module_wrapper(), + utils::ty::variable(), + utils::ty::mapping(), + utils::ty::rc_contract_env(), + parse_quote!(Rc), + ] + .iter() + .map(|ty| utils::syn::type_to_ident(ty).map(|i| vec![ty.clone(), parse_quote!(#i)])) + .collect::, _>>()?; + let valid_types = valid_types.into_iter().flatten().collect::>(); + + if valid_types.iter().find(|t| test_utils::eq(t, &non_generic_ty)).is_some() { + return Ok(()); + } + + Err(syn::Error::new(ty.span(), "message")) } } +pub struct EnumeratedTypedField { + pub idx: u8, + pub ident: syn::Ident, + pub ty: syn::Type +} + +try_parse!(syn::ItemImpl => ModuleIR); + impl ModuleIR { - pub fn self_code(&self) -> &ItemImpl { + pub fn self_code(&self) -> &syn::ItemImpl { &self.code } diff --git a/odra-macros/src/lib.rs b/odra-macros/src/lib.rs index c4347c49..fb99fd9c 100644 --- a/odra-macros/src/lib.rs +++ b/odra-macros/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(box_patterns)] +#![feature(box_patterns, result_flattening)] use ast::*; use ir::ModuleIR; diff --git a/odra-macros/src/test_utils.rs b/odra-macros/src/test_utils.rs index 489f8c58..728d22e2 100644 --- a/odra-macros/src/test_utils.rs +++ b/odra-macros/src/test_utils.rs @@ -1,6 +1,6 @@ use quote::{quote, ToTokens}; -use crate::ir::ModuleIR; +use crate::ir::{ModuleIR, StructIR}; pub fn mock_module() -> ModuleIR { let module = quote! { @@ -20,11 +20,30 @@ pub fn mock_module() -> ModuleIR { ModuleIR::try_from(&module).unwrap() } +pub fn mock_module_definition() -> StructIR { + let module = quote!( + pub struct CounterPack { + env: Rc, + counter0: ModuleWrapper, + counter1: ModuleWrapper, + counter2: ModuleWrapper, + counters: Variable, + counters_map: Mapping + } + ); + StructIR::try_from(&module).unwrap() +} + pub fn assert_eq(a: A, b: B) { - fn parse(e: T) -> String { - let e = e.to_token_stream().to_string(); - let e = syn::parse_file(&e).unwrap(); - prettyplease::unparse(&e) - } pretty_assertions::assert_eq!(parse(a), parse(b)); } + +pub fn eq(a: A, b: B) -> bool { + parse(a) == parse(b) +} + +fn parse(e: T) -> String { + let e = e.to_token_stream().to_string(); + let e = syn::parse_file(&e).unwrap(); + prettyplease::unparse(&e) +} \ No newline at end of file diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index b7e8d98f..ebf94768 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -11,3 +11,28 @@ pub fn u512_zero() -> syn::Expr { pub fn parse_bytes(data_ident: &syn::Ident) -> syn::Expr { parse_quote!(odra::ToBytes::to_bytes(&#data_ident).map(Into::into).unwrap()) } + +pub fn new_variable(env_ident: &syn::Ident, idx: u8) -> syn::Expr { + let ty = super::ty::variable(); + new_type(&ty, env_ident, idx) +} + +pub fn new_mapping(env_ident: &syn::Ident, idx: u8) -> syn::Expr { + let ty = super::ty::mapping(); + new_type(&ty, env_ident, idx) +} + +pub fn new_module_wrapper(env_ident: &syn::Ident, idx: u8) -> syn::Expr { + let ty = super::ty::module_wrapper(); + new_type(&ty, env_ident, idx) +} + +pub fn new_type(ty: &syn::Type, env_ident: &syn::Ident, idx: u8) -> syn::Expr { + let rc = rc_clone(env_ident); + dbg!(quote::quote!(#ty::new(#rc, #idx)).to_string()); + parse_quote!(#ty::new(#rc, #idx)) +} + +fn rc_clone(ident: &syn::Ident) -> syn::Expr { + parse_quote!(Rc::clone(&#ident)) +} diff --git a/odra-macros/src/utils/syn.rs b/odra-macros/src/utils/syn.rs index 55f7676a..15543432 100644 --- a/odra-macros/src/utils/syn.rs +++ b/odra-macros/src/utils/syn.rs @@ -1,15 +1,11 @@ use syn::{parse_quote, spanned::Spanned}; pub fn ident_from_impl(impl_code: &syn::ItemImpl) -> Result { - match &*impl_code.self_ty { - syn::Type::Path(type_path) => { - Ok(type_path.path.segments.last().expect("dupa").ident.clone()) - } - ty => Err(syn::Error::new( - ty.span(), - "Only support impl for type path" - )) - } + type_to_ident(&impl_code.self_ty) +} + +pub fn ident_from_struct(struct_code: &syn::ItemStruct) -> syn::Ident { + struct_code.ident.clone() } pub fn function_arg_names(function: &syn::ImplItemFn) -> Vec { @@ -67,6 +63,79 @@ pub fn function_return_type(function: &syn::ImplItemFn) -> syn::ReturnType { function.sig.output.clone() } +pub fn struct_fields_ident(item: &syn::ItemStruct) -> Result, syn::Error> { + if let syn::Fields::Named(named) = &item.fields { + let err_msg = "Invalid field. Module fields must be named"; + named + .named + .iter() + .map(|f| f.ident.clone().ok_or(syn::Error::new(f.span(), err_msg))) + .collect::, syn::Error>>() + } else { + Err(syn::Error::new_spanned( + &item.fields, + "Invalid fields. Module fields must be named" + )) + } +} + +pub fn struct_fields(item: &syn::ItemStruct) -> Result, syn::Error> { + if let syn::Fields::Named(named) = &item.fields { + let err_msg = "Invalid field. Module fields must be named"; + named + .named + .iter() + .map(|f| { + f.ident.clone() + .ok_or(syn::Error::new_spanned(f, err_msg)) + .map(|i| (i, f.ty.clone())) + }) + .collect() + } else { + Err(syn::Error::new_spanned( + &item.fields, + "Invalid fields. Module fields must be named" + )) + } +} + pub fn visibility_pub() -> syn::Visibility { parse_quote!(pub) } + +pub fn type_to_ident(ty: &syn::Type) -> Result{ + match ty { + syn::Type::Path(type_path) => type_path + .path + .segments + .last() + .map(|seg| seg.ident.clone()) + .ok_or(syn::Error::new(type_path.span(), "Invalid type path")), + ty => Err(syn::Error::new( + ty.span(), + "Only support impl for type path" + )) + } +} + +pub fn clear_generics(ty: &syn::Type) -> Result { + match ty { + syn::Type::Path(type_path) => clear_path(type_path).map(|p| syn::Type::Path(p)), + ty => Err(syn::Error::new( + ty.span(), + "Only support impl for type path" + )) + } +} + +fn clear_path(ty: &syn::TypePath) -> Result { + let mut owned_ty = ty.to_owned(); + + let mut segment = owned_ty.path + .segments + .last_mut() + .ok_or(syn::Error::new(ty.span(), "Invalid type path"))?; + segment.arguments = syn::PathArguments::None; + + Ok(owned_ty) +} \ No newline at end of file diff --git a/odra-macros/src/utils/ty.rs b/odra-macros/src/utils/ty.rs index 8c77b239..0e2c4095 100644 --- a/odra-macros/src/utils/ty.rs +++ b/odra-macros/src/utils/ty.rs @@ -1,5 +1,6 @@ use syn::parse_quote; + pub fn address() -> syn::Type { parse_quote!(odra::Address) } @@ -8,6 +9,15 @@ pub fn contract_env() -> syn::Type { parse_quote!(odra::ContractEnv) } +pub fn rc_contract_env() -> syn::Type { + parse_quote!(Rc) +} + +pub fn is_rc_contract_env(ty: &syn::Type) -> bool { + let rc: syn::Type = parse_quote!(Rc); + test_utils::eq(ty, &rc) || test_utils::eq(ty, &rc_contract_env()) +} + pub fn from_bytes() -> syn::Type { parse_quote!(odra::FromBytes) } @@ -43,3 +53,23 @@ pub fn contract_call_result() -> syn::Type { pub fn odra_error() -> syn::Type { parse_quote!(odra::OdraError) } + +pub fn module_wrapper() -> syn::Type { + parse_quote!(odra::ModuleWrapper) +} + +pub fn module() -> syn::Type { + parse_quote!(odra::module::Module) +} + +pub fn variable() -> syn::Type { + parse_quote!(odra::Variable) +} + +pub fn mapping() -> syn::Type { + parse_quote!(odra::Mapping) +} + +pub fn super_path(ident: syn::Ident) -> syn::Type { + parse_quote!(super::#ident) +} From d53024225cbd32c773d808c4a271810f9f21d7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Thu, 23 Nov 2023 17:25:49 +0100 Subject: [PATCH 2/6] generate Module trait implementation --- odra-macros/src/ast/host_ref_item.rs | 2 +- odra-macros/src/ast/module_item.rs | 15 +++++---- odra-macros/src/ast/test_parts.rs | 2 +- odra-macros/src/ir/mod.rs | 46 ++++++++++++++++------------ odra-macros/src/test_utils.rs | 15 +++------ odra-macros/src/utils/expr.rs | 15 --------- odra-macros/src/utils/string.rs | 17 +++------- odra-macros/src/utils/syn.rs | 12 +++++--- odra-macros/src/utils/ty.rs | 14 --------- 9 files changed, 54 insertions(+), 84 deletions(-) diff --git a/odra-macros/src/ast/host_ref_item.rs b/odra-macros/src/ast/host_ref_item.rs index dfcdd1bd..00095518 100644 --- a/odra-macros/src/ast/host_ref_item.rs +++ b/odra-macros/src/ast/host_ref_item.rs @@ -198,7 +198,7 @@ mod ref_item_tests { self.env.last_call().contract_last_call(self.address) } - pub fn try_total_supply(&self) -> Result { + pub fn try_total_supply(&self) -> Result { self.env.call_contract( self.address, odra::CallDef::new( diff --git a/odra-macros/src/ast/module_item.rs b/odra-macros/src/ast/module_item.rs index decf77fb..de16da14 100644 --- a/odra-macros/src/ast/module_item.rs +++ b/odra-macros/src/ast/module_item.rs @@ -1,7 +1,10 @@ -use quote::{TokenStreamExt, ToTokens}; +use quote::{ToTokens, TokenStreamExt}; use syn::parse_quote; -use crate::{ir::{StructIR, EnumeratedTypedField}, utils}; +use crate::{ + ir::{EnumeratedTypedField, StructIR}, + utils +}; use super::parts_utils::UseSuperItem; @@ -116,7 +119,7 @@ struct ModuleInstanceItem { #[syn(braced)] braces: syn::token::Brace, #[syn(in = braces)] - values: syn::punctuated::Punctuated, + values: syn::punctuated::Punctuated } impl TryFrom<&'_ StructIR> for ModuleInstanceItem { @@ -159,7 +162,7 @@ mod test { let expected = quote!( mod __counter_pack_module { use super::*; - + impl odra::module::Module for CounterPack { fn new(env: Rc) -> Self { let counter0 = ModuleWrapper::new(Rc::clone(&env), 0u8); @@ -176,12 +179,12 @@ mod test { counters_map } } - + fn env(&self) -> Rc { self.env.clone() } } - } + } ); let actual = ModuleModItem::try_from(&module).unwrap(); test_utils::assert_eq(actual, expected); diff --git a/odra-macros/src/ast/test_parts.rs b/odra-macros/src/ast/test_parts.rs index 11715807..5de2637d 100644 --- a/odra-macros/src/ast/test_parts.rs +++ b/odra-macros/src/ast/test_parts.rs @@ -117,7 +117,7 @@ mod test { self.env.last_call().contract_last_call(self.address) } - pub fn try_total_supply(&self) -> Result { + pub fn try_total_supply(&self) -> Result { self.env.call_contract( self.address, odra::CallDef::new( diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index 5f5cfa78..2a26946c 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -1,6 +1,6 @@ -use crate::{utils, test_utils}; +use crate::utils; use proc_macro2::Ident; -use quote::{format_ident, ToTokens}; +use quote::format_ident; use syn::{parse_quote, spanned::Spanned}; const CONSTRUCTOR_NAME: &str = "init"; @@ -34,54 +34,60 @@ impl StructIR { utils::syn::ident_from_struct(&self.code) } - pub fn module_str(&self) -> String { - self.module_ident().to_string() - } - pub fn module_mod_ident(&self) -> syn::Ident { format_ident!( "__{}_module", - odra_utils::camel_to_snake(self.module_ident()) + utils::string::camel_to_snake(self.module_ident()) ) } pub fn typed_fields(&self) -> Result, syn::Error> { let fields = utils::syn::struct_fields(&self.code)?; + let fields = fields + .iter() + .filter(|(i, _)| i != &utils::ident::env()) + .collect::>(); + for (_, ty) in &fields { Self::validate_ty(ty)?; } fields - .into_iter() + .iter() .enumerate() - .map(|(idx, (ident, ty))| Ok(EnumeratedTypedField { - idx: idx as u8, - ident, - ty: utils::syn::clear_generics(&ty)?, - })) + .map(|(idx, (ident, ty))| { + Ok(EnumeratedTypedField { + idx: idx as u8, + ident: ident.clone(), + ty: utils::syn::clear_generics(&ty)? + }) + }) .collect() } fn validate_ty(ty: &syn::Type) -> Result<(), syn::Error> { let non_generic_ty = utils::syn::clear_generics(ty)?; + // both odra::Variable and Variable (Mapping, ModuleWrapper) are valid. let valid_types = vec![ utils::ty::module_wrapper(), utils::ty::variable(), utils::ty::mapping(), - utils::ty::rc_contract_env(), - parse_quote!(Rc), ] - .iter() - .map(|ty| utils::syn::type_to_ident(ty).map(|i| vec![ty.clone(), parse_quote!(#i)])) - .collect::, _>>()?; + .iter() + .map(|ty| utils::syn::last_segment_ident(ty).map(|i| vec![ty.clone(), parse_quote!(#i)])) + .collect::, _>>()?; let valid_types = valid_types.into_iter().flatten().collect::>(); - if valid_types.iter().find(|t| test_utils::eq(t, &non_generic_ty)).is_some() { + if valid_types + .iter() + .find(|t| utils::string::eq(t, &non_generic_ty)) + .is_some() + { return Ok(()); } - Err(syn::Error::new(ty.span(), "message")) + Err(syn::Error::new(ty.span(), "Invalid module type")) } } diff --git a/odra-macros/src/test_utils.rs b/odra-macros/src/test_utils.rs index 728d22e2..e91525b2 100644 --- a/odra-macros/src/test_utils.rs +++ b/odra-macros/src/test_utils.rs @@ -35,15 +35,10 @@ pub fn mock_module_definition() -> StructIR { } pub fn assert_eq(a: A, b: B) { + fn parse(e: T) -> String { + let e = e.to_token_stream().to_string(); + let e = syn::parse_file(&e).unwrap(); + prettyplease::unparse(&e) + } pretty_assertions::assert_eq!(parse(a), parse(b)); } - -pub fn eq(a: A, b: B) -> bool { - parse(a) == parse(b) -} - -fn parse(e: T) -> String { - let e = e.to_token_stream().to_string(); - let e = syn::parse_file(&e).unwrap(); - prettyplease::unparse(&e) -} \ No newline at end of file diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index ebf94768..5b503019 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -12,21 +12,6 @@ pub fn parse_bytes(data_ident: &syn::Ident) -> syn::Expr { parse_quote!(odra::ToBytes::to_bytes(&#data_ident).map(Into::into).unwrap()) } -pub fn new_variable(env_ident: &syn::Ident, idx: u8) -> syn::Expr { - let ty = super::ty::variable(); - new_type(&ty, env_ident, idx) -} - -pub fn new_mapping(env_ident: &syn::Ident, idx: u8) -> syn::Expr { - let ty = super::ty::mapping(); - new_type(&ty, env_ident, idx) -} - -pub fn new_module_wrapper(env_ident: &syn::Ident, idx: u8) -> syn::Expr { - let ty = super::ty::module_wrapper(); - new_type(&ty, env_ident, idx) -} - pub fn new_type(ty: &syn::Type, env_ident: &syn::Ident, idx: u8) -> syn::Expr { let rc = rc_clone(env_ident); dbg!(quote::quote!(#ty::new(#rc, #idx)).to_string()); diff --git a/odra-macros/src/utils/string.rs b/odra-macros/src/utils/string.rs index 7805a6b5..ed13d8c4 100644 --- a/odra-macros/src/utils/string.rs +++ b/odra-macros/src/utils/string.rs @@ -1,20 +1,13 @@ use convert_case::{Boundary, Case, Casing}; +use quote::ToTokens; -/// Converts a camel-cased &str to String. -/// -/// # Example -/// -/// ``` -/// use odra_utils::camel_to_snake; -/// -/// let camel = "ContractName"; -/// let result = camel_to_snake(camel); -/// -/// assert_eq!(&result, "contract_name"); -/// ``` pub fn camel_to_snake(text: T) -> String { text.to_string() .from_case(Case::UpperCamel) .without_boundaries(&[Boundary::UpperDigit, Boundary::LowerDigit]) .to_case(Case::Snake) } + +pub fn eq(a: A, b: B) -> bool { + a.to_token_stream().to_string() == b.to_token_stream().to_string() +} diff --git a/odra-macros/src/utils/syn.rs b/odra-macros/src/utils/syn.rs index 15543432..23d00ad7 100644 --- a/odra-macros/src/utils/syn.rs +++ b/odra-macros/src/utils/syn.rs @@ -1,7 +1,7 @@ use syn::{parse_quote, spanned::Spanned}; pub fn ident_from_impl(impl_code: &syn::ItemImpl) -> Result { - type_to_ident(&impl_code.self_ty) + last_segment_ident(&impl_code.self_ty) } pub fn ident_from_struct(struct_code: &syn::ItemStruct) -> syn::Ident { @@ -86,7 +86,8 @@ pub fn struct_fields(item: &syn::ItemStruct) -> Result syn::Visibility { parse_quote!(pub) } -pub fn type_to_ident(ty: &syn::Type) -> Result{ +pub fn last_segment_ident(ty: &syn::Type) -> Result { match ty { syn::Type::Path(type_path) => type_path .path @@ -131,11 +132,12 @@ pub fn clear_generics(ty: &syn::Type) -> Result { fn clear_path(ty: &syn::TypePath) -> Result { let mut owned_ty = ty.to_owned(); - let mut segment = owned_ty.path + let mut segment = owned_ty + .path .segments .last_mut() .ok_or(syn::Error::new(ty.span(), "Invalid type path"))?; segment.arguments = syn::PathArguments::None; Ok(owned_ty) -} \ No newline at end of file +} diff --git a/odra-macros/src/utils/ty.rs b/odra-macros/src/utils/ty.rs index 0e2c4095..09e1b73b 100644 --- a/odra-macros/src/utils/ty.rs +++ b/odra-macros/src/utils/ty.rs @@ -1,6 +1,5 @@ use syn::parse_quote; - pub fn address() -> syn::Type { parse_quote!(odra::Address) } @@ -9,15 +8,6 @@ pub fn contract_env() -> syn::Type { parse_quote!(odra::ContractEnv) } -pub fn rc_contract_env() -> syn::Type { - parse_quote!(Rc) -} - -pub fn is_rc_contract_env(ty: &syn::Type) -> bool { - let rc: syn::Type = parse_quote!(Rc); - test_utils::eq(ty, &rc) || test_utils::eq(ty, &rc_contract_env()) -} - pub fn from_bytes() -> syn::Type { parse_quote!(odra::FromBytes) } @@ -69,7 +59,3 @@ pub fn variable() -> syn::Type { pub fn mapping() -> syn::Type { parse_quote!(odra::Mapping) } - -pub fn super_path(ident: syn::Ident) -> syn::Type { - parse_quote!(super::#ident) -} From 6fa1a7fbd6f13abac197cbda215a19faecea0b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Fri, 24 Nov 2023 12:01:37 +0100 Subject: [PATCH 3/6] generate odra::Module in examples --- examples2/src/counter_pack.rs | 42 +------------------------- examples2/src/erc20.rs | 23 +------------- odra-macros/src/ast/mod.rs | 1 + odra-macros/src/ast/module_item.rs | 2 +- odra-macros/src/ir/mod.rs | 9 ++++-- odra-macros/src/lib.rs | 48 ++++++++++++++++++++++-------- odra-macros/src/utils/syn.rs | 2 +- 7 files changed, 47 insertions(+), 80 deletions(-) diff --git a/examples2/src/counter_pack.rs b/examples2/src/counter_pack.rs index 9b3cc0bb..34e6523d 100644 --- a/examples2/src/counter_pack.rs +++ b/examples2/src/counter_pack.rs @@ -4,6 +4,7 @@ use odra::ContractEnv; use odra::Mapping; use odra::ModuleWrapper; +#[odra_macros::module] pub struct CounterPack { env: Rc, counter0: ModuleWrapper, @@ -60,47 +61,6 @@ impl CounterPack { } } -// autogenerated -mod odra_core_module { - use super::*; - - impl Module for CounterPack { - fn new(env: Rc) -> Self { - let counter0 = ModuleWrapper::new(Rc::clone(&env), 0); - let counter1 = ModuleWrapper::new(Rc::clone(&env), 1); - let counter2 = ModuleWrapper::new(Rc::clone(&env), 2); - let counter3 = ModuleWrapper::new(Rc::clone(&env), 3); - let counter4 = ModuleWrapper::new(Rc::clone(&env), 4); - let counter5 = ModuleWrapper::new(Rc::clone(&env), 5); - let counter6 = ModuleWrapper::new(Rc::clone(&env), 6); - let counter7 = ModuleWrapper::new(Rc::clone(&env), 7); - let counter8 = ModuleWrapper::new(Rc::clone(&env), 8); - let counter9 = ModuleWrapper::new(Rc::clone(&env), 9); - let counters = Mapping::new(Rc::clone(&env), 10); - let counters_map = Mapping::new(Rc::clone(&env), 11); - Self { - env, - counter0, - counter1, - counter2, - counter3, - counter4, - counter5, - counter6, - counter7, - counter8, - counter9, - counters, - counters_map - } - } - - fn env(&self) -> Rc { - self.env.clone() - } - } -} - #[cfg(odra_module = "CounterPack")] #[cfg(target_arch = "wasm32")] mod __counter_pack_wasm_parts { diff --git a/examples2/src/erc20.rs b/examples2/src/erc20.rs index 9cc64f7f..0c6a7835 100644 --- a/examples2/src/erc20.rs +++ b/examples2/src/erc20.rs @@ -36,6 +36,7 @@ impl From for OdraError { } } +#[odra_macros::module] pub struct Erc20 { env: Rc, total_supply: Variable, @@ -138,28 +139,6 @@ impl Erc20 { } } -// autogenerated for general purpose module. -mod __erc20_module { - use super::Erc20; - use odra::prelude::*; - - impl odra::module::Module for Erc20 { - fn new(env: Rc) -> Self { - let total_supply = odra::Variable::new(Rc::clone(&env), 1); - let balances = odra::Mapping::new(Rc::clone(&env), 2); - Self { - env, - total_supply, - balances - } - } - - fn env(&self) -> Rc { - self.env.clone() - } - } -} - #[cfg(odra_module = "Erc20")] mod __erc20_schema { use odra::{contract_def::ContractBlueprint2, prelude::String}; diff --git a/odra-macros/src/ast/mod.rs b/odra-macros/src/ast/mod.rs index 39b40a5c..bff0073c 100644 --- a/odra-macros/src/ast/mod.rs +++ b/odra-macros/src/ast/mod.rs @@ -7,5 +7,6 @@ mod ref_item; mod ref_utils; mod test_parts; +pub(crate) use module_item::ModuleModItem; pub(crate) use ref_item::RefItem; pub(crate) use test_parts::{TestParts, TestPartsReexport}; diff --git a/odra-macros/src/ast/module_item.rs b/odra-macros/src/ast/module_item.rs index de16da14..001ecb2b 100644 --- a/odra-macros/src/ast/module_item.rs +++ b/odra-macros/src/ast/module_item.rs @@ -9,7 +9,7 @@ use crate::{ use super::parts_utils::UseSuperItem; #[derive(syn_derive::ToTokens)] -struct ModuleModItem { +pub struct ModuleModItem { mod_token: syn::token::Mod, mod_ident: syn::Ident, #[syn(braced)] diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index 2a26946c..8986a4e0 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -26,6 +26,10 @@ macro_rules! try_parse { try_parse!(syn::ItemStruct => StructIR); impl StructIR { + pub fn self_code(&self) -> &syn::ItemStruct { + &self.code + } + pub fn field_names(&self) -> Result, syn::Error> { utils::syn::struct_fields_ident(&self.code) } @@ -59,7 +63,7 @@ impl StructIR { Ok(EnumeratedTypedField { idx: idx as u8, ident: ident.clone(), - ty: utils::syn::clear_generics(&ty)? + ty: utils::syn::clear_generics(ty)? }) }) .collect() @@ -81,8 +85,7 @@ impl StructIR { if valid_types .iter() - .find(|t| utils::string::eq(t, &non_generic_ty)) - .is_some() + .any(|t| utils::string::eq(t, &non_generic_ty)) { return Ok(()); } diff --git a/odra-macros/src/lib.rs b/odra-macros/src/lib.rs index fb99fd9c..5f4527aa 100644 --- a/odra-macros/src/lib.rs +++ b/odra-macros/src/lib.rs @@ -1,8 +1,10 @@ #![feature(box_patterns, result_flattening)] use ast::*; -use ir::ModuleIR; +use ir::{ModuleIR, StructIR}; use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use syn::spanned::Spanned; mod ast; mod ir; @@ -12,20 +14,24 @@ mod utils; #[proc_macro_attribute] pub fn module(_attr: TokenStream, item: TokenStream) -> TokenStream { - match module_impl(item) { - Ok(result) => result, - Err(e) => e.to_compile_error() + let stream: TokenStream2 = item.into(); + if let Ok(ir) = ModuleIR::try_from(&stream) { + return handle_result(module_impl(ir)); } - .into() + if let Ok(ir) = StructIR::try_from(&stream) { + return handle_result(module_struct(ir)); + } + handle_result(Err(syn::Error::new( + stream.span(), + "Struct or impl block expected" + ))) } -fn module_impl(item: TokenStream) -> Result { - let module_ir = ModuleIR::try_from(&item.into())?; - - let code = module_ir.self_code(); - let ref_item = RefItem::try_from(&module_ir)?; - let test_parts = TestParts::try_from(&module_ir)?; - let test_parts_reexport = TestPartsReexport::try_from(&module_ir)?; +fn module_impl(ir: ModuleIR) -> Result { + let code = ir.self_code(); + let ref_item = RefItem::try_from(&ir)?; + let test_parts = TestParts::try_from(&ir)?; + let test_parts_reexport = TestPartsReexport::try_from(&ir)?; Ok(quote::quote! { #code @@ -34,3 +40,21 @@ fn module_impl(item: TokenStream) -> Result Result { + let code = ir.self_code(); + let module_mod = ModuleModItem::try_from(&ir)?; + + Ok(quote::quote!( + #code + #module_mod + )) +} + +fn handle_result(result: Result) -> TokenStream { + match result { + Ok(stream) => stream, + Err(e) => e.to_compile_error() + } + .into() +} diff --git a/odra-macros/src/utils/syn.rs b/odra-macros/src/utils/syn.rs index 23d00ad7..e741b0c8 100644 --- a/odra-macros/src/utils/syn.rs +++ b/odra-macros/src/utils/syn.rs @@ -121,7 +121,7 @@ pub fn last_segment_ident(ty: &syn::Type) -> Result { pub fn clear_generics(ty: &syn::Type) -> Result { match ty { - syn::Type::Path(type_path) => clear_path(type_path).map(|p| syn::Type::Path(p)), + syn::Type::Path(type_path) => clear_path(type_path).map(syn::Type::Path), ty => Err(syn::Error::new( ty.span(), "Only support impl for type path" From df7c391cf81309093fe7caba02ed4c3250e670fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Fri, 24 Nov 2023 12:23:49 +0100 Subject: [PATCH 4/6] update paths, fix styling --- odra-macros/src/ast/module_item.rs | 2 +- odra-macros/src/utils/expr.rs | 1 - odra-macros/src/utils/ty.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/odra-macros/src/ast/module_item.rs b/odra-macros/src/ast/module_item.rs index 001ecb2b..70e5bbe0 100644 --- a/odra-macros/src/ast/module_item.rs +++ b/odra-macros/src/ast/module_item.rs @@ -163,7 +163,7 @@ mod test { mod __counter_pack_module { use super::*; - impl odra::module::Module for CounterPack { + impl odra::Module for CounterPack { fn new(env: Rc) -> Self { let counter0 = ModuleWrapper::new(Rc::clone(&env), 0u8); let counter1 = ModuleWrapper::new(Rc::clone(&env), 1u8); diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index 5b503019..00315c8c 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -14,7 +14,6 @@ pub fn parse_bytes(data_ident: &syn::Ident) -> syn::Expr { pub fn new_type(ty: &syn::Type, env_ident: &syn::Ident, idx: u8) -> syn::Expr { let rc = rc_clone(env_ident); - dbg!(quote::quote!(#ty::new(#rc, #idx)).to_string()); parse_quote!(#ty::new(#rc, #idx)) } diff --git a/odra-macros/src/utils/ty.rs b/odra-macros/src/utils/ty.rs index 09e1b73b..a6fdf1a6 100644 --- a/odra-macros/src/utils/ty.rs +++ b/odra-macros/src/utils/ty.rs @@ -49,7 +49,7 @@ pub fn module_wrapper() -> syn::Type { } pub fn module() -> syn::Type { - parse_quote!(odra::module::Module) + parse_quote!(odra::Module) } pub fn variable() -> syn::Type { From f32bd6c6a6e2d8028b629d8b18129eebfb0f7e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Tue, 28 Nov 2023 08:46:50 +0100 Subject: [PATCH 5/6] update ModuleIR --- odra-macros/src/ir/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index 8986a4e0..4ca4def7 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -130,7 +130,6 @@ impl ModuleIR { )) } - #[allow(dead_code)] pub fn deployer_ident(&self) -> Result { let module_ident = self.module_ident()?; Ok(Ident::new( @@ -140,9 +139,11 @@ impl ModuleIR { } pub fn test_parts_mod_ident(&self) -> Result { - self.module_ident() - .map(crate::utils::string::camel_to_snake) - .map(|ident| format_ident!("__{}_test_parts", ident)) + let module_ident = self.module_ident()?; + Ok(Ident::new( + &format!("__{}_test_parts", crate::utils::string::camel_to_snake(&module_ident)), + module_ident.span() + )) } pub fn functions(&self) -> Vec { From edc7698733251352093bfcc3002506966668cb40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Tue, 28 Nov 2023 08:54:36 +0100 Subject: [PATCH 6/6] fix formatting --- odra-macros/src/ir/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index 4ca4def7..58236c8c 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -141,7 +141,10 @@ impl ModuleIR { pub fn test_parts_mod_ident(&self) -> Result { let module_ident = self.module_ident()?; Ok(Ident::new( - &format!("__{}_test_parts", crate::utils::string::camel_to_snake(&module_ident)), + &format!( + "__{}_test_parts", + crate::utils::string::camel_to_snake(&module_ident) + ), module_ident.span() )) }