diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ecd4e44..5e0f5b78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.1](https://github.com/CosmWasm/sylvia/compare/sylvia-derive-v0.7.0...sylvia-derive-v0.7.1) - 2023-08-14 + +### Fixed +- Prefix interface proxy with module as Path + ## [0.7.0](https://github.com/CosmWasm/sylvia/compare/sylvia-derive-v0.6.1...sylvia-derive-v0.7.0) - 2023-08-01 ### Added diff --git a/Cargo.lock b/Cargo.lock index 32c18a99..21b1da90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -829,7 +829,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "sylvia" -version = "0.7.0" +version = "0.7.1" dependencies = [ "anyhow", "cosmwasm-schema", @@ -848,7 +848,7 @@ dependencies = [ [[package]] name = "sylvia-derive" -version = "0.7.0" +version = "0.7.1" dependencies = [ "convert_case", "proc-macro-crate", diff --git a/Cargo.toml b/Cargo.toml index 5040619f..7ab4f4de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ exclude = ["examples/*"] resolver = "2" [workspace.package] -version = "0.7.0" +version = "0.7.1" [workspace.dependencies] -sylvia-derive = { version = "0.7.0", path = "sylvia-derive" } +sylvia-derive = { version = "0.7.1", path = "sylvia-derive" } diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 4d1b9ca5..f057d32a 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -976,7 +976,7 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "sylvia" -version = "0.7.0" +version = "0.7.1" dependencies = [ "anyhow", "cosmwasm-schema", @@ -993,7 +993,7 @@ dependencies = [ [[package]] name = "sylvia-derive" -version = "0.7.0" +version = "0.7.1" dependencies = [ "convert_case", "proc-macro-crate", diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs index adabf1be..6fe658c5 100644 --- a/sylvia-derive/src/input.rs +++ b/sylvia-derive/src/input.rs @@ -28,6 +28,7 @@ pub struct ImplInput<'a> { generics: Vec<&'a GenericParam>, custom: Custom<'a>, override_entry_points: OverrideEntryPoints, + interfaces: Interfaces, } impl<'a> TraitInput<'a> { @@ -60,7 +61,7 @@ impl<'a> TraitInput<'a> { pub fn process(&self) -> TokenStream { let messages = self.emit_messages(); let multitest_helpers = self.emit_helpers(); - let remote = Remote::new(&[]).emit(); + let remote = Remote::new(&Interfaces::default()).emit(); let querier = MsgVariants::new(self.item.as_variants(), &self.generics).emit_querier(); #[cfg(not(tarpaulin_include))] @@ -128,6 +129,7 @@ impl<'a> ImplInput<'a> { let custom = Custom::new(&item.attrs); let override_entry_points = OverrideEntryPoints::new(&item.attrs); + let interfaces = Interfaces::new(item); Self { attributes, @@ -136,6 +138,7 @@ impl<'a> ImplInput<'a> { error, custom, override_entry_points, + interfaces, } } @@ -149,6 +152,7 @@ impl<'a> ImplInput<'a> { &self.generics, &self.custom, &self.override_entry_points, + &self.interfaces, ) .emit() } else { @@ -187,7 +191,7 @@ impl<'a> ImplInput<'a> { multitest_helpers: TokenStream, ) -> TokenStream { let messages = self.emit_messages(); - let remote = Remote::new(&self.item.attrs).emit(); + let remote = Remote::new(interfaces).emit(); let querier = variants.emit_querier(); let querier_from_impl = interfaces.emit_querier_from_impl(); @@ -253,7 +257,15 @@ impl<'a> ImplInput<'a> { } fn emit_glue_msg(&self, name: &Ident, msg_ty: MsgType) -> TokenStream { - GlueMessage::new(name, self.item, msg_ty, &self.error, &self.custom).emit() + GlueMessage::new( + name, + self.item, + msg_ty, + &self.error, + &self.custom, + &self.interfaces, + ) + .emit() } fn emit_querier_for_bound_impl( diff --git a/sylvia-derive/src/interfaces.rs b/sylvia-derive/src/interfaces.rs index 9125c0fa..94f0d575 100644 --- a/sylvia-derive/src/interfaces.rs +++ b/sylvia-derive/src/interfaces.rs @@ -1,18 +1,27 @@ -use proc_macro2::TokenStream; +use convert_case::{Case, Casing}; +use proc_macro2::{Ident, TokenStream}; use proc_macro_error::emit_error; use quote::quote; use syn::parse::{Parse, Parser}; use syn::spanned::Spanned; -use syn::ItemImpl; +use syn::{ItemImpl, Path, Type}; use crate::crate_module; -use crate::parser::ContractMessageAttr; +use crate::parser::{ContractMessageAttr, MsgType}; +#[derive(Debug, Default)] pub struct Interfaces { interfaces: Vec, } impl Interfaces { + fn merge_module_with_name(message_attr: &ContractMessageAttr, name: &syn::Ident) -> syn::Ident { + // ContractMessageAttr will fail to parse empty `#[messsages()]` attribute so we can safely unwrap here + let syn::PathSegment { ident, .. } = &message_attr.module.segments.last().unwrap(); + let module_name = ident.to_string().to_case(Case::UpperCamel); + syn::Ident::new(&format!("{}{}", module_name, name), name.span()) + } + pub fn new(source: &ItemImpl) -> Self { let interfaces: Vec<_> = source .attrs @@ -41,10 +50,8 @@ impl Interfaces { pub fn emit_querier_from_impl(&self) -> Vec { let sylvia = crate_module(); - self.interfaces - .iter() - .map(|interface| { - let ContractMessageAttr { module, .. } = interface; + self.as_modules() + .map(|module| { quote! { impl<'a, C: #sylvia ::cw_std::CustomQuery> From<&'a BoundQuerier<'a, C>> for #module ::BoundQuerier<'a, C> { fn from(querier: &'a BoundQuerier<'a, C>) -> Self { @@ -55,4 +62,100 @@ impl Interfaces { }) .collect() } + + pub fn emit_proxy_accessors(&self, mt_app: &Type) -> Vec { + self.as_modules() + .map(|module| { + // ContractMessageAttr will fail to parse empty `#[messsages()]` attribute so we can safely unwrap here + let module_name = &module.segments.last().unwrap().ident; + let method_name = Ident::new(&format!("{}_proxy", module_name), module_name.span()); + let proxy_name = Ident::new( + &format!("{}Proxy", module_name.to_string().to_case(Case::UpperCamel)), + module_name.span(), + ); + + quote! { + pub fn #method_name (&self) -> #module ::trait_utils:: #proxy_name <'app, #mt_app> { + #module ::trait_utils:: #proxy_name ::new(self.contract_addr.clone(), self.app) + } + } + }) + .collect() + } + + pub fn emit_glue_message_variants( + &self, + msg_ty: &MsgType, + msg_name: &Ident, + ) -> Vec { + self.interfaces + .iter() + .map(|interface| { + let ContractMessageAttr { + module, + exec_generic_params, + query_generic_params, + variant, + .. + } = interface; + + let generics = match msg_ty { + MsgType::Exec => exec_generic_params.as_slice(), + MsgType::Query => query_generic_params.as_slice(), + _ => &[], + }; + + let enum_name = Self::merge_module_with_name(interface, msg_name); + quote! { #variant(#module :: #enum_name<#(#generics,)*>) } + }) + .collect() + } + + pub fn emit_messages_call(&self, msg_name: &Ident) -> Vec { + self.interfaces + .iter() + .map(|interface| { + let enum_name = Self::merge_module_with_name(interface, msg_name); + let module = &interface.module; + quote! { &#module :: #enum_name :: messages()} + }) + .collect() + } + + pub fn emit_deserialization_attempts(&self, msg_name: &Ident) -> Vec { + self.interfaces + .iter() + .map(|interface| { + let ContractMessageAttr { + module, variant, .. + } = interface; + let enum_name = Self::merge_module_with_name(interface, msg_name); + + quote! { + let msgs = &#module :: #enum_name ::messages(); + if msgs.into_iter().any(|msg| msg == &recv_msg_name) { + match val.deserialize_into() { + Ok(msg) => return Ok(Self:: #variant (msg)), + Err(err) => return Err(D::Error::custom(err)).map(Self:: #variant), + }; + } + } + }) + .collect() + } + + pub fn emit_response_schemas_calls(&self, msg_name: &Ident) -> Vec { + self.interfaces + .iter() + .map(|interface| { + let enum_name = Self::merge_module_with_name(interface, msg_name); + let module = &interface.module; + quote! { #module :: #enum_name :: response_schemas_impl()} + }) + .collect() + } + + pub fn as_modules(&self) -> impl Iterator { + self.interfaces.iter().map(|interface| &interface.module) + } } diff --git a/sylvia-derive/src/message.rs b/sylvia-derive/src/message.rs index 1ff77c47..35f4df21 100644 --- a/sylvia-derive/src/message.rs +++ b/sylvia-derive/src/message.rs @@ -1,5 +1,6 @@ use crate::check_generics::CheckGenerics; use crate::crate_module; +use crate::interfaces::Interfaces; use crate::parser::{ parse_associated_custom_type, parse_struct_message, ContractErrorAttr, ContractMessageAttr, Custom, MsgAttr, MsgType, OverrideEntryPoint, OverrideEntryPoints, @@ -846,57 +847,30 @@ impl<'a> MsgField<'a> { /// Glue message is the message composing Exec/Query messages from several traits #[derive(Debug)] pub struct GlueMessage<'a> { - interfaces: Vec, name: &'a Ident, contract: &'a Type, msg_ty: MsgType, error: &'a Type, custom: &'a Custom<'a>, + interfaces: &'a Interfaces, } impl<'a> GlueMessage<'a> { - #[cfg(not(tarpaulin_include))] - // Lack of coverage here is false negative due to usage in closures - fn merge_module_with_name(module: &syn::Path, name: &syn::Ident) -> syn::Ident { - let segments = &module.segments; - assert!(!segments.is_empty()); - - let syn::PathSegment { ident, .. } = &segments[0]; - let module_name = ident.to_string().to_case(Case::UpperCamel); - syn::Ident::new(&format!("{}{}", module_name, name), name.span()) - } - pub fn new( name: &'a Ident, source: &'a ItemImpl, msg_ty: MsgType, error: &'a Type, custom: &'a Custom, + interfaces: &'a Interfaces, ) -> Self { - let interfaces: Vec<_> = source - .attrs - .iter() - .filter(|attr| attr.path.is_ident("messages")) - .filter_map(|attr| { - let interface = match ContractMessageAttr::parse.parse2(attr.tokens.clone()) { - Ok(interface) => interface, - Err(err) => { - emit_error!(attr.span(), err); - return None; - } - }; - - Some(interface) - }) - .collect(); - GlueMessage { - interfaces, name, contract: &source.self_ty, msg_ty, error, custom, + interfaces, } } @@ -904,50 +878,26 @@ impl<'a> GlueMessage<'a> { let sylvia = crate_module(); let Self { - interfaces, name, contract, msg_ty, error, custom, + interfaces, } = self; let contract = StripGenerics.fold_type((*contract).clone()); let contract_name = Ident::new(&format!("Contract{}", name), name.span()); - let variants = interfaces.iter().map(|interface| { - let ContractMessageAttr { - module, - exec_generic_params, - query_generic_params, - variant, - .. - } = interface; - - let generics = match msg_ty { - MsgType::Exec => exec_generic_params.as_slice(), - MsgType::Query => query_generic_params.as_slice(), - _ => &[], - }; - - let enum_name = GlueMessage::merge_module_with_name(module, name); - quote! { #variant(#module :: #enum_name<#(#generics,)*>) } - }); + let variants = interfaces.emit_glue_message_variants(msg_ty, name); let msg_name = quote! {#contract ( #name)}; - let mut interface_names: Vec = interfaces - .iter() - .map(|interface| { - let ContractMessageAttr { module, .. } = interface; + let mut messages_call_on_all_variants: Vec = + interfaces.emit_messages_call(name); + messages_call_on_all_variants.push(quote! {&#name :: messages()}); - let enum_name = GlueMessage::merge_module_with_name(module, name); - quote! { &#module :: #enum_name :: messages()} - }) - .collect(); - interface_names.push(quote! {&#name :: messages()}); + let variants_cnt = messages_call_on_all_variants.len(); - let interfaces_cnt = interface_names.len(); - - let dispatch_arms = interfaces.iter().map(|interface| { + let dispatch_arms = interfaces.interfaces().iter().map(|interface| { let ContractMessageAttr { variant, has_custom_msg, @@ -964,23 +914,7 @@ impl<'a> GlueMessage<'a> { let dispatch_arm = quote! {#contract_name :: #contract (msg) =>msg.dispatch(contract, ctx)}; - #[cfg(not(tarpaulin_include))] - let deserialization_attempts = interfaces.iter().map(|interface| { - let ContractMessageAttr { - module, variant, .. - } = interface; - let enum_name = GlueMessage::merge_module_with_name(module, name); - - quote! { - let msgs = &#module :: #enum_name ::messages(); - if msgs.into_iter().any(|msg| msg == &recv_msg_name) { - match val.deserialize_into() { - Ok(msg) => return Ok(Self:: #variant (msg)), - Err(err) => return Err(D::Error::custom(err)).map(Self:: #variant), - }; - } - } - }); + let interfaces_deserialization_attempts = interfaces.emit_deserialization_attempts(name); #[cfg(not(tarpaulin_include))] let contract_deserialization_attempt = quote! { @@ -996,16 +930,8 @@ impl<'a> GlueMessage<'a> { let ctx_type = msg_ty.emit_ctx_type(&custom.query_or_default()); let ret_type = msg_ty.emit_result_type(&custom.msg_or_default(), error); - let mut response_schemas: Vec = interfaces - .iter() - .map(|interface| { - let ContractMessageAttr { module, .. } = interface; - - let enum_name = GlueMessage::merge_module_with_name(module, name); - quote! { #module :: #enum_name :: response_schemas_impl()} - }) - .collect(); - response_schemas.push(quote! {#name :: response_schemas_impl()}); + let mut response_schemas_calls = interfaces.emit_response_schemas_calls(name); + response_schemas_calls.push(quote! {#name :: response_schemas_impl()}); let response_schemas = match name.to_string().as_str() { "QueryMsg" => { @@ -1015,7 +941,7 @@ impl<'a> GlueMessage<'a> { #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for #contract_name { fn response_schemas_impl() -> std::collections::BTreeMap { - let responses = [#(#response_schemas),*]; + let responses = [#(#response_schemas_calls),*]; responses.into_iter().flatten().collect() } } @@ -1045,7 +971,7 @@ impl<'a> GlueMessage<'a> { ctx: #ctx_type, ) -> #ret_type { const _: () = { - let msgs: [&[&str]; #interfaces_cnt] = [#(#interface_names),*]; + let msgs: [&[&str]; #variants_cnt] = [#(#messages_call_on_all_variants),*]; #sylvia ::utils::assert_no_intersection(msgs); }; @@ -1072,15 +998,16 @@ impl<'a> GlueMessage<'a> { if map.len() != 1 { return Err(D::Error::custom(format!("Expected exactly one message. Received {}", map.len()))) } + // Due to earlier size check of map this unwrap is safe let recv_msg_name = map.into_iter().next().unwrap(); if let #sylvia ::serde_value::Value::String(recv_msg_name) = &recv_msg_name .0 { - #(#deserialization_attempts)* + #(#interfaces_deserialization_attempts)* #contract_deserialization_attempt } - let msgs: [&[&str]; #interfaces_cnt] = [#(#interface_names),*]; + let msgs: [&[&str]; #variants_cnt] = [#(#messages_call_on_all_variants),*]; let mut err_msg = msgs.into_iter().flatten().fold( // It might be better to forward the error or serialization, but we just // deserialized it from JSON, not reason to expect failure here. diff --git a/sylvia-derive/src/multitest.rs b/sylvia-derive/src/multitest.rs index 015f1ee5..8b871d36 100644 --- a/sylvia-derive/src/multitest.rs +++ b/sylvia-derive/src/multitest.rs @@ -1,17 +1,18 @@ -use convert_case::{Case, Casing}; use proc_macro2::{Ident, TokenStream}; use proc_macro_error::emit_error; use quote::quote; use syn::parse::{Parse, Parser}; use syn::spanned::Spanned; -use syn::{FnArg, GenericParam, ImplItem, ItemImpl, ItemTrait, Pat, PatType, Path, Type}; +use syn::{ + parse_quote, FnArg, GenericParam, ImplItem, ItemImpl, ItemTrait, Pat, PatType, Path, Type, +}; use crate::check_generics::CheckGenerics; use crate::crate_module; +use crate::interfaces::Interfaces; use crate::message::MsgField; use crate::parser::{ - parse_struct_message, ContractMessageAttr, Custom, MsgAttr, MsgType, OverrideEntryPoint, - OverrideEntryPoints, + parse_struct_message, Custom, MsgAttr, MsgType, OverrideEntryPoint, OverrideEntryPoints, }; use crate::utils::{extract_return_type, process_fields}; @@ -36,6 +37,7 @@ pub struct MultitestHelpers<'a> { proxy_name: Ident, custom: &'a Custom<'a>, override_entry_points: &'a OverrideEntryPoints, + interfaces: &'a Interfaces, } fn interface_name(source: &ItemImpl) -> &Ident { @@ -65,6 +67,7 @@ impl<'a> MultitestHelpers<'a> { generics: &'a [&'a GenericParam], custom: &'a Custom, override_entry_points: &'a OverrideEntryPoints, + interfaces: &'a Interfaces, ) -> Self { let mut is_migrate = false; let mut reply = None; @@ -203,6 +206,7 @@ impl<'a> MultitestHelpers<'a> { proxy_name, custom, override_entry_points, + interfaces, } } @@ -211,9 +215,9 @@ impl<'a> MultitestHelpers<'a> { messages, error_type, proxy_name, - source, is_trait, custom, + interfaces, .. } = self; let sylvia = crate_module(); @@ -224,7 +228,7 @@ impl<'a> MultitestHelpers<'a> { let custom_msg = custom.msg_or_default(); #[cfg(not(tarpaulin_include))] - let mt_app = quote! { + let mt_app: Type = parse_quote! { #sylvia ::cw_multi_test::App< BankT, ApiT, @@ -284,37 +288,7 @@ impl<'a> MultitestHelpers<'a> { let contract_block = self.generate_contract_helpers(); - let interfaces: Vec<_> = source - .attrs - .iter() - .filter(|attr| attr.path.is_ident("messages")) - .filter_map(|attr| { - let interface = match ContractMessageAttr::parse.parse2(attr.tokens.clone()) { - Ok(interface) => { - let ContractMessageAttr { module, .. } = interface; - assert!(!module.segments.is_empty()); - let module_name = &module.segments[0].ident; - let method_name = Ident::new(&format!("{}_proxy", module_name), module_name.span()); - let proxy_name = Ident::new( - &format!("{}Proxy", module_name.to_string().to_case(Case::UpperCamel)), - module_name.span(), - ); - - quote! { - pub fn #method_name (&self) -> #module ::trait_utils:: #proxy_name <'app, #mt_app> { - #module ::trait_utils:: #proxy_name ::new(self.contract_addr.clone(), self.app) - } - } - } - Err(err) => { - emit_error!(attr.span(), err); - return None; - } - }; - - Some(interface) - }) - .collect(); + let proxy_accessors = interfaces.emit_proxy_accessors(&mt_app); #[cfg(not(tarpaulin_include))] { @@ -358,7 +332,7 @@ impl<'a> MultitestHelpers<'a> { #(#messages)* - #(#interfaces)* + #(#proxy_accessors)* } impl<'app, BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> @@ -402,8 +376,8 @@ impl<'a> MultitestHelpers<'a> { let Self { messages, error_type, - source, custom, + interfaces, .. } = self; @@ -413,24 +387,7 @@ impl<'a> MultitestHelpers<'a> { let proxy_name = &self.proxy_name; let trait_name = Ident::new(&format!("{}", interface_name), interface_name.span()); - let modules: Vec<_> = source - .attrs - .iter() - .filter(|attr| attr.path.is_ident("messages")) - .filter_map( - |attr| match ContractMessageAttr::parse.parse2(attr.tokens.clone()) { - Ok(interface) => { - let ContractMessageAttr { module, .. } = &interface; - assert!(!module.segments.is_empty()); - Some(module.segments[0].ident.clone()) - } - Err(err) => { - emit_error!(attr.span(), err); - None - } - }, - ) - .collect(); + let modules: Vec<&Path> = interfaces.as_modules().collect(); #[cfg(not(tarpaulin_include))] let module = match modules.len() { @@ -442,11 +399,15 @@ impl<'a> MultitestHelpers<'a> { quote! {#module ::} } _ => { - emit_error!( - source.span(), - "Only one #[messages] attribute is allowed per contract" - ); - return quote! {}; + let first = &modules[0]; + for redefined in &modules[1..] { + emit_error!( + redefined, "The attribute `messages` is redefined"; + note = first.span() => "Previous definition of the attribute `messsages`"; + note = "Only one `messages` attribute can exist on an interface implementation on contract" + ); + } + quote! {} } }; diff --git a/sylvia-derive/src/remote.rs b/sylvia-derive/src/remote.rs index 9799bc9b..4d673f1f 100644 --- a/sylvia-derive/src/remote.rs +++ b/sylvia-derive/src/remote.rs @@ -1,41 +1,23 @@ use proc_macro2::TokenStream; -use proc_macro_error::emit_error; use quote::quote; -use syn::parse::{Parse, Parser}; -use syn::spanned::Spanned; -use syn::Attribute; use crate::crate_module; +use crate::interfaces::Interfaces; use crate::parser::ContractMessageAttr; -pub struct Remote { - interfaces: Vec, +pub struct Remote<'a> { + interfaces: &'a Interfaces, } -impl Remote { - pub fn new(attrs: &[Attribute]) -> Self { - let interfaces: Vec<_> = attrs - .iter() - .filter(|attr| attr.path.is_ident("messages")) - .filter_map(|attr| { - let interface = match ContractMessageAttr::parse.parse2(attr.tokens.clone()) { - Ok(interface) => interface, - Err(err) => { - emit_error!(attr.span(), err); - return None; - } - }; - - Some(interface) - }) - .collect(); +impl<'a> Remote<'a> { + pub fn new(interfaces: &'a Interfaces) -> Self { Self { interfaces } } pub fn emit(&self) -> TokenStream { let sylvia = crate_module(); - let from_implementations = self.interfaces.iter().map(|interface| { + let from_implementations = self.interfaces.interfaces().iter().map(|interface| { let ContractMessageAttr { module, .. } = interface; quote! { diff --git a/sylvia/tests/querier.rs b/sylvia/tests/querier.rs index dca2567f..191e8800 100644 --- a/sylvia/tests/querier.rs +++ b/sylvia/tests/querier.rs @@ -37,6 +37,7 @@ pub mod counter { } #[contract(module=super)] + #[messages(crate::counter as Counter)] impl Counter for super::CounterContract<'_> { type Error = StdError;