Repository: CosmWasm/sylvia
Pull request #311 (2024-02-27 11:46:37)

feat: Error on missing module for `impl Interface for Contract`

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/CosmWasm/sylvia/pull/311?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) Report
Attention: Patch coverage is `80.76923%` with `5 lines` in your changes are missing coverage. Please review.
> Project coverage is 87.16%. Comparing base [(`3c1bf9a`)](https://app.codecov.io/gh/CosmWasm/sylvia/commit/3c1bf9a6983084c8d1cbfcb22b13a4df6731e958?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) to head [(`c445883`)](https://app.codecov.io/gh/CosmWasm/sylvia/pull/311?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).

| [Files](https://app.codecov.io/gh/CosmWasm/sylvia/pull/311?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) | Patch % | Lines |
|---|---|---|
| [sylvia-derive/src/parser/contract.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/311?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvcGFyc2VyL2NvbnRyYWN0LnJz) | 54.54% | [5 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/311?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |

<details><summary>Additional details and impacted files</summary>


```diff
@@                        Coverage Diff                         @@
##           feat/add_support_for_sv_prefix     #311      +/-   ##
==================================================================
- Coverage                           87.18%   87.16%   -0.02%     
==================================================================
  Files                                  37       37              
  Lines                                2029     2034       +5     
==================================================================
+ Hits                                 1769     1773       +4     
- Misses                                260      261       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/CosmWasm/sylvia/pull/311?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).


Patch:
diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs
index 55bb910..1fd49f2 100644
--- a/sylvia-derive/src/input.rs
+++ b/sylvia-derive/src/input.rs
@@ -16,7 +16,6 @@ use crate::parser::{
 };
 use crate::querier::{ContractQuerier, ImplQuerier, TraitQuerier};
 use crate::remote::{ContractRemote, InterfaceRemote};
-use crate::utils::is_trait;
 use crate::variant_descs::AsVariantDescs;
 
 /// Preprocessed `interface` macro input
@@ -26,17 +25,6 @@ pub struct TraitInput<'a> {
     associated_types: AssociatedTypes<'a>,
 }
 
-/// Preprocessed `contract` macro input for non-trait impl block
-pub struct ImplInput<'a> {
-    attributes: &'a ContractArgs,
-    error: ContractErrorAttr,
-    item: &'a ItemImpl,
-    generics: Vec<&'a GenericParam>,
-    custom: Custom,
-    override_entry_points: Vec<OverrideEntryPoint>,
-    interfaces: Interfaces,
-}
-
 impl<'a> TraitInput<'a> {
     pub fn new(item: &'a ItemTrait) -> Self {
         if !item.generics.params.is_empty() {
@@ -139,34 +127,32 @@ impl<'a> TraitInput<'a> {
     }
 }
 
-impl<'a> ImplInput<'a> {
+/// Preprocessed `contract` macro input for impl trait block
+pub struct ImplTraitInput<'a> {
+    attributes: &'a ContractArgs,
+    item: &'a ItemImpl,
+    generics: Vec<&'a GenericParam>,
+    custom: Custom,
+    interfaces: Interfaces,
+}
+
+impl<'a> ImplTraitInput<'a> {
     pub fn new(attributes: &'a ContractArgs, item: &'a ItemImpl) -> Self {
         let generics = item.generics.params.iter().collect();
         let parsed_attrs = ParsedSylviaAttributes::new(item.attrs.iter());
-        let error = parsed_attrs.error_attrs.unwrap_or_default();
         let custom = parsed_attrs.custom_attr.unwrap_or_default();
-        let override_entry_points = parsed_attrs.override_entry_point_attrs;
         let interfaces = Interfaces::new(item);
 
         Self {
             attributes,
             item,
             generics,
-            error,
             custom,
-            override_entry_points,
             interfaces,
         }
     }
 
     pub fn process(&self) -> TokenStream {
-        match is_trait(self.item) {
-            true => self.process_interface(),
-            false => self.process_contract(),
-        }
-    }
-
-    fn process_interface(&self) -> TokenStream {
         let multitest_helpers = self.emit_multitest_helpers();
         let querier = self.emit_querier_for_bound_impl();
 
@@ -181,7 +167,74 @@ impl<'a> ImplInput<'a> {
         }
     }
 
-    fn process_contract(&self) -> TokenStream {
+    fn emit_querier_for_bound_impl(&self) -> TokenStream {
+        let variants_args =
+            MsgVariants::<GenericParam>::new(self.item.as_variants(), MsgType::Query, &[], &None);
+        let associated_types = ImplAssociatedTypes::new(self.item);
+        ImplQuerier::new(
+            self.item,
+            &variants_args,
+            &associated_types,
+            &self.interfaces,
+            &self.attributes.module,
+        )
+        .emit()
+    }
+
+    fn emit_multitest_helpers(&self) -> TokenStream {
+        if !cfg!(feature = "mt") {
+            return quote! {};
+        }
+
+        let Self {
+            item,
+            custom,
+            interfaces,
+            ..
+        } = self;
+        let generic_params = &self.generics;
+
+        ImplMtHelpers::new(
+            item,
+            generic_params,
+            custom,
+            interfaces,
+            &self.attributes.module,
+        )
+        .emit()
+    }
+}
+
+/// Preprocessed `contract` macro input for non-trait impl block
+pub struct ImplInput<'a> {
+    error: ContractErrorAttr,
+    item: &'a ItemImpl,
+    generics: Vec<&'a GenericParam>,
+    custom: Custom,
+    override_entry_points: Vec<OverrideEntryPoint>,
+    interfaces: Interfaces,
+}
+
+impl<'a> ImplInput<'a> {
+    pub fn new(item: &'a ItemImpl) -> Self {
+        let generics = item.generics.params.iter().collect();
+        let parsed_attrs = ParsedSylviaAttributes::new(item.attrs.iter());
+        let error = parsed_attrs.error_attrs.unwrap_or_default();
+        let custom = parsed_attrs.custom_attr.unwrap_or_default();
+        let override_entry_points = parsed_attrs.override_entry_point_attrs;
+        let interfaces = Interfaces::new(item);
+
+        Self {
+            item,
+            generics,
+            error,
+            custom,
+            override_entry_points,
+            interfaces,
+        }
+    }
+
+    pub fn process(&self) -> TokenStream {
         let Self {
             item,
             generics,
@@ -267,21 +320,6 @@ impl<'a> ImplInput<'a> {
         .emit()
     }
 
-    fn emit_querier_for_bound_impl(&self) -> TokenStream {
-        let contract_module = self.attributes.module.as_ref();
-        let variants_args =
-            MsgVariants::<GenericParam>::new(self.item.as_variants(), MsgType::Query, &[], &None);
-        let associated_types = ImplAssociatedTypes::new(self.item);
-        ImplQuerier::new(
-            self.item,
-            &variants_args,
-            &associated_types,
-            &self.interfaces,
-            &contract_module,
-        )
-        .emit()
-    }
-
     fn emit_multitest_helpers(&self) -> TokenStream {
         if !cfg!(feature = "mt") {
             return quote! {};
@@ -291,17 +329,10 @@ impl<'a> ImplInput<'a> {
             item,
             custom,
             override_entry_points,
-            interfaces,
             ..
         } = self;
-        let contract_module = self.attributes.module.as_ref();
-        let generic_params = &self.generics;
 
-        if is_trait(item) {
-            ImplMtHelpers::new(item, generic_params, custom, interfaces, &contract_module).emit()
-        } else {
-            ContractMtHelpers::new(item, generic_params, custom, override_entry_points.clone())
-                .emit()
-        }
+        let generic_params = &self.generics;
+        ContractMtHelpers::new(item, generic_params, custom, override_entry_points.clone()).emit()
     }
 }
diff --git a/sylvia-derive/src/lib.rs b/sylvia-derive/src/lib.rs
index 59e1be7..faa2ab3 100644
--- a/sylvia-derive/src/lib.rs
+++ b/sylvia-derive/src/lib.rs
@@ -1,4 +1,4 @@
-use input::{ImplInput, TraitInput};
+use input::{ImplInput, ImplTraitInput, TraitInput};
 use proc_macro::TokenStream;
 use proc_macro2::TokenStream as TokenStream2;
 use proc_macro_error::proc_macro_error;
@@ -235,10 +235,13 @@ pub fn contract(attr: TokenStream, item: TokenStream) -> TokenStream {
 
 fn contract_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
     fn inner(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
-        let attrs: parser::ContractArgs = parse2(attr)?;
         let input: ItemImpl = parse2(item)?;
-
-        let expanded = ImplInput::new(&attrs, &input).process();
+        let expanded = if input.trait_.is_some() {
+            let attrs: parser::ContractArgs = parse2(attr)?;
+            ImplTraitInput::new(&attrs, &input).process()
+        } else {
+            ImplInput::new(&input).process()
+        };
         let input = StripInput.fold_item_impl(input);
 
         Ok(quote! {
diff --git a/sylvia-derive/src/parser/contract.rs b/sylvia-derive/src/parser/contract.rs
index 0a4e68a..ab2aec1 100644
--- a/sylvia-derive/src/parser/contract.rs
+++ b/sylvia-derive/src/parser/contract.rs
@@ -1,3 +1,4 @@
+use proc_macro_error::abort;
 use syn::parse::{Error, Nothing, Parse, ParseStream};
 use syn::{Ident, Path, Result, Token};
 
@@ -5,32 +6,28 @@ use syn::{Ident, Path, Result, Token};
 pub struct ContractArgs {
     /// Module in which contract impl block is defined.
     /// Used only while implementing `Interface` on `Contract`.
-    pub module: Option<Path>,
+    pub module: Path,
 }
 
 impl Parse for ContractArgs {
     fn parse(input: ParseStream) -> Result<Self> {
-        let mut module = None;
-
-        while !input.is_empty() {
-            let attr: Ident = input.parse()?;
+        let maybe_module = input.parse().and_then(|attr: Ident| -> Result<Path> {
             let _: Token![=] = input.parse()?;
-
             if attr == "module" {
-                module = Some(input.parse()?);
+                input.parse()
             } else {
-                return Err(Error::new(attr.span(), "expected `module`"));
-            }
-
-            if input.peek(Token![,]) {
-                let _: Token![,] = input.parse()?;
-            } else if !input.is_empty() {
-                return Err(input.error("Unexpected token, comma expected"));
+                Err(Error::new(attr.span(), "Missing `module` attribute"))
             }
-        }
-
+        });
+        let module: Path = match maybe_module {
+            Ok(module) => module,
+            Err(e) => abort!(
+                e.span(), "The module path needs to be provided `#[contract(module=path::to::contract)`.";
+                note = "Implementing interface on a contract requires to point the path to the contract structure.";
+                note = "Parsing error: {}", e
+            ),
+        };
         let _: Nothing = input.parse()?;
-
         Ok(ContractArgs { module })
     }
 }
diff --git a/sylvia-derive/src/querier.rs b/sylvia-derive/src/querier.rs
index efde2aa..4d9580a 100644
--- a/sylvia-derive/src/querier.rs
+++ b/sylvia-derive/src/querier.rs
@@ -96,7 +96,7 @@ pub struct ImplQuerier<'a, Generic> {
     variants: &'a MsgVariants<'a, Generic>,
     associated_types: &'a ImplAssociatedTypes<'a>,
     interfaces: &'a Interfaces,
-    contract_module: &'a Option<&'a Path>,
+    contract_module: &'a Path,
 }
 
 impl<'a, Generic> ImplQuerier<'a, Generic>
@@ -108,7 +108,7 @@ where
         variants: &'a MsgVariants<'a, Generic>,
         associated_types: &'a ImplAssociatedTypes,
         interfaces: &'a Interfaces,
-        contract_module: &'a Option<&'a Path>,
+        contract_module: &'a Path,
     ) -> Self {
         Self {
             source,
@@ -153,14 +153,11 @@ where
         let querier = trait_module
             .map(|module| quote! { #module ::sv::Querier })
             .unwrap_or_else(|| quote! { sv::Querier });
-        let bound_querier = contract_module
-            .map(|module| quote! { #module ::sv::BoundQuerier})
-            .unwrap_or_else(|| quote! { sv::BoundQuerier });
 
         let types_definition = associated_types.as_item_types();
 
         quote! {
-            impl <'a, C: #sylvia ::cw_std::CustomQuery, #generic_params > #querier for #bound_querier<'a, C, #generic_params > #where_clause {
+            impl <'a, C: #sylvia ::cw_std::CustomQuery, #generic_params > #querier for #contract_module ::sv::BoundQuerier<'a, C, #generic_params > #where_clause {
                 #(#types_definition)*
 
                 #(#methods_impl)*
diff --git a/sylvia-derive/src/utils.rs b/sylvia-derive/src/utils.rs
index ffe3e6a..02b82a4 100644
--- a/sylvia-derive/src/utils.rs
+++ b/sylvia-derive/src/utils.rs
@@ -4,8 +4,8 @@ use quote::{quote, ToTokens};
 use syn::spanned::Spanned;
 use syn::visit::Visit;
 use syn::{
-    parse_quote, FnArg, GenericArgument, GenericParam, ItemImpl, Path, PathArguments, ReturnType,
-    Signature, Type, WhereClause, WherePredicate,
+    parse_quote, FnArg, GenericArgument, GenericParam, Path, PathArguments, ReturnType, Signature,
+    Type, WhereClause, WherePredicate,
 };
 
 use crate::check_generics::{CheckGenerics, GetPath};
@@ -125,7 +125,3 @@ pub fn emit_bracketed_generics<GenericT: ToTokens>(unbonded_generics: &[GenericT
         false => quote! { < #(#unbonded_generics,)* > },
     }
 }
-
-pub fn is_trait(item: &ItemImpl) -> bool {
-    item.trait_.is_some()
-}


Test patch:
diff --git a/sylvia-derive/src/multitest.rs b/sylvia-derive/src/multitest.rs
index ea571bd..a22ca34 100644
--- a/sylvia-derive/src/multitest.rs
+++ b/sylvia-derive/src/multitest.rs
@@ -11,7 +11,7 @@ use crate::parser::attributes::msg::MsgType;
 use crate::parser::{
     Custom, FilteredOverrideEntryPoints, OverrideEntryPoint, ParsedSylviaAttributes,
 };
-use crate::utils::{emit_bracketed_generics, is_trait};
+use crate::utils::emit_bracketed_generics;
 use crate::variant_descs::AsVariantDescs;
 
 fn interface_name(source: &ItemImpl) -> &Ident {
@@ -100,23 +100,11 @@ impl<'a> ContractMtHelpers<'a> {
             where_clause,
         );
 
-        let error_type: Type = if is_trait(source) {
-            let associated_error = source.items.iter().find_map(|item| match item {
-                ImplItem::Type(ty) if ty.ident == "Error" => Some(&ty.ty),
-                _ => None,
-            });
-            match associated_error {
-                Some(error) => parse_quote!(#error),
-                // This should never happen as the `interface` macro requires the trait to have an associated `Error` type
-                None => unreachable!(),
-            }
-        } else {
-            let error = ParsedSylviaAttributes::new(source.attrs.iter())
-                .error_attrs
-                .unwrap_or_default()
-                .error;
-            parse_quote! { #error }
-        };
+        let error_type = ParsedSylviaAttributes::new(source.attrs.iter())
+            .error_attrs
+            .unwrap_or_default()
+            .error;
+        let error_type = parse_quote! { #error_type };
 
         let contract = &source.self_ty;
         let contract_name = extract_contract_name(contract);
@@ -627,7 +615,7 @@ pub struct ImplMtHelpers<'a> {
     query_variants: MsgVariants<'a, GenericParam>,
     sudo_variants: MsgVariants<'a, GenericParam>,
     where_clause: &'a Option<syn::WhereClause>,
-    contract_module: &'a Option<&'a Path>,
+    contract_module: &'a Path,
     contract_name: &'a Ident,
 }
 
@@ -637,7 +625,7 @@ impl<'a> ImplMtHelpers<'a> {
         generic_params: &'a [&'a GenericParam],
         custom: &'a Custom,
         interfaces: &'a Interfaces,
-        contract_module: &'a Option<&'a Path>,
+        contract_module: &'a Path,
     ) -> Self {
         let where_clause = &source.generics.where_clause;
         let exec_variants = MsgVariants::new(
@@ -782,10 +770,6 @@ impl<'a> ImplMtHelpers<'a> {
             &associated_items,
         );
 
-        let contract_module = match contract_module {
-            Some(contract_module) => quote! { #contract_module :: },
-            None => quote! {},
-        };
         let contract_proxy = Ident::new(&format!("{}Proxy", contract_name), contract_name.span());
 
         let where_predicates = where_clause
@@ -802,7 +786,7 @@ impl<'a> ImplMtHelpers<'a> {
                     #(#sudo_methods_declarations)*
                 }
 
-                impl<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT, #(#generic_params,)* > #trait_name< #mt_app, #(#generic_params,)* > for #contract_module sv::multitest_utils:: #contract_proxy <'_, #mt_app, #(#generic_params,)* >
+                impl<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT, #(#generic_params,)* > #trait_name< #mt_app, #(#generic_params,)* > for #contract_module :: sv::multitest_utils:: #contract_proxy <'_, #mt_app, #(#generic_params,)* >
                 where
                     CustomT: #sylvia ::cw_multi_test::Module,
                     WasmT: #sylvia ::cw_multi_test::Wasm<CustomT::ExecT, CustomT::QueryT>,