Repository: CosmWasm/sylvia (rust)

Issue:
Methods in multitest proxy are generated using interfaces module as Ident instead of Path
# Current behavior

If we specify module in which interface is defined as `Path` `sylvia` will treat it as `Ident`.

```rust
#[contract]
#[messages(crate::interface as Interface)]
impl Interface for Contract {
...
} 
```

will generate f.e.

```rust
let msg = crate::ExecMsg::new(...);
```

# Expected behavior

`Sylvia` should use full path.

```rust
let msg = crate::interface::ExecMsg::new(...);
```


Pull request description:
fix: Use interfaces module as Path instead of Ident
closes #212

Patch:
diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs
index 87f8deb..930e774 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();
 
@@ -252,7 +256,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 9125c0f..94f0d57 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<ContractMessageAttr>,
 }
 
 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<TokenStream> {
         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<TokenStream> {
+        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<TokenStream> {
+        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<TokenStream> {
+        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<TokenStream> {
+        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<TokenStream> {
+        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<Item = &Path> {
+        self.interfaces.iter().map(|interface| &interface.module)
+    }
 }
diff --git a/sylvia-derive/src/message.rs b/sylvia-derive/src/message.rs
index f57f592..4a51077 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_struct_message, ContractErrorAttr, ContractMessageAttr, Custom, MsgAttr, MsgType,
     OverrideEntryPoint, OverrideEntryPoints,
@@ -836,57 +837,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<ContractMessageAttr>,
     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,
         }
     }
 
@@ -894,50 +868,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<TokenStream> = interfaces
-            .iter()
-            .map(|interface| {
-                let ContractMessageAttr { module, .. } = interface;
+        let mut messages_call_on_all_variants: Vec<TokenStream> =
+            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,
@@ -954,23 +904,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! {
@@ -986,16 +920,8 @@ impl<'a> GlueMessage<'a> {
         let ctx_type = msg_ty.emit_ctx_type();
         let ret_type = msg_ty.emit_result_type(&custom.msg(), error);
 
-        let mut response_schemas: Vec<TokenStream> = 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" => {
@@ -1005,7 +931,7 @@ impl<'a> GlueMessage<'a> {
                         #[cfg(not(target_arch = "wasm32"))]
                         impl cosmwasm_schema::QueryResponses for #contract_name {
                             fn response_schemas_impl() -> std::collections::BTreeMap<String, #sylvia ::schemars::schema::RootSchema> {
-                                let responses = [#(#response_schemas),*];
+                                let responses = [#(#response_schemas_calls),*];
                                 responses.into_iter().flatten().collect()
                             }
                         }
@@ -1035,7 +961,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);
                         };
 
@@ -1062,15 +988,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/remote.rs b/sylvia-derive/src/remote.rs
index 9799bc9..4d673f1 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<ContractMessageAttr>,
+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! {


Test patch:
diff --git a/sylvia-derive/src/multitest.rs b/sylvia-derive/src/multitest.rs
index ab077fd..89fb8cd 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();
         #[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/tests/querier.rs b/sylvia/tests/querier.rs
index dca2567..191e880 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;