Skip to content

Commit

Permalink
feat: Support generics on messages attribute in main contract macro
Browse files Browse the repository at this point in the history
call
  • Loading branch information
jawoznia committed Oct 9, 2023
1 parent 8470f7f commit 4e37906
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 27 deletions.
62 changes: 43 additions & 19 deletions sylvia-derive/src/interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ pub struct Interfaces {
}

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
Expand Down Expand Up @@ -107,28 +100,46 @@ impl Interfaces {
.collect()
}

pub fn emit_messages_call(&self, msg_name: &Ident) -> Vec<TokenStream> {
pub fn emit_messages_call(&self, msg_ty: &MsgType) -> Vec<TokenStream> {
let sylvia = crate_module();

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()}
let ContractMessageAttr {
module, generics, ..
} = interface;
let generics = if !generics.is_empty() {
quote! { <#(#generics,)*> }
} else {
quote! {}
};
let type_name = msg_ty.as_accessor_name();
quote! {
&<#module :: InterfaceTypes #generics as #sylvia ::types::InterfaceMessages> :: #type_name :: messages()
}
})
.collect()
}

pub fn emit_deserialization_attempts(&self, msg_name: &Ident) -> Vec<TokenStream> {
pub fn emit_deserialization_attempts(&self, msg_ty: &MsgType) -> Vec<TokenStream> {
let sylvia = crate_module();

self.interfaces
.iter()
.map(|interface| {
let ContractMessageAttr {
module, variant, ..
module, variant, generics, ..
} = interface;
let enum_name = Self::merge_module_with_name(interface, msg_name);
let generics = if !generics.is_empty() {
quote! { <#(#generics,)*> }
} else {
quote! {}
};

let type_name = msg_ty.as_accessor_name();
quote! {
let msgs = &#module :: #enum_name ::messages();
let msgs = &<#module :: InterfaceTypes #generics as #sylvia ::types::InterfaceMessages> :: #type_name :: messages();
if msgs.into_iter().any(|msg| msg == &recv_msg_name) {
match val.deserialize_into() {
Ok(msg) => return Ok(Self:: #variant (msg)),
Expand All @@ -140,13 +151,26 @@ impl Interfaces {
.collect()
}

pub fn emit_response_schemas_calls(&self, msg_name: &Ident) -> Vec<TokenStream> {
pub fn emit_response_schemas_calls(&self, msg_ty: &MsgType) -> Vec<TokenStream> {
let sylvia = crate_module();

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()}
let ContractMessageAttr {
module, generics, ..
} = interface;

let generics = if !generics.is_empty() {
quote! { <#(#generics,)*> }
} else {
quote! {}
};

let type_name = msg_ty.as_accessor_name();
quote! {
<#module :: InterfaceTypes #generics as #sylvia ::types::InterfaceMessages> :: #type_name :: response_schemas_impl()
}
})
.collect()
}
Expand Down
6 changes: 3 additions & 3 deletions sylvia-derive/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,7 @@ impl<'a> GlueMessage<'a> {

let msg_name = quote! {#contract ( #name)};
let mut messages_call_on_all_variants: Vec<TokenStream> =
interfaces.emit_messages_call(name);
interfaces.emit_messages_call(msg_ty);
messages_call_on_all_variants.push(quote! {&#name :: messages()});

let variants_cnt = messages_call_on_all_variants.len();
Expand Down Expand Up @@ -1002,7 +1002,7 @@ impl<'a> GlueMessage<'a> {

let dispatch_arm = quote! {#enum_name :: #contract (msg) => msg.dispatch(contract, ctx)};

let interfaces_deserialization_attempts = interfaces.emit_deserialization_attempts(name);
let interfaces_deserialization_attempts = interfaces.emit_deserialization_attempts(msg_ty);

#[cfg(not(tarpaulin_include))]
let contract_deserialization_attempt = quote! {
Expand All @@ -1018,7 +1018,7 @@ 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_calls = interfaces.emit_response_schemas_calls(name);
let mut response_schemas_calls = interfaces.emit_response_schemas_calls(msg_ty);
response_schemas_calls.push(quote! {#name :: response_schemas_impl()});

let response_schemas = match name.to_string().as_str() {
Expand Down
8 changes: 8 additions & 0 deletions sylvia-derive/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@ impl MsgType {
MsgType::Sudo => todo!(),
}
}

pub fn as_accessor_name(&self) -> Option<Type> {
match self {
MsgType::Exec => Some(parse_quote! { Exec }),
MsgType::Query => Some(parse_quote! { Query }),
_ => None,
}
}
}

impl PartialEq<MsgType> for MsgAttr {
Expand Down
18 changes: 13 additions & 5 deletions sylvia/tests/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ pub mod cw1_contract {
use sylvia::types::InstantiateCtx;
use sylvia_derive::contract;

use crate::{ExternalMsg, ExternalQuery};

pub struct Cw1Contract;

#[contract]
#[messages(crate::cw1, <ExternalMsg, ExternalMsg, ExternalQuery> as Cw1)]
/// Required if interface returns generic `Response`
#[sv::custom(msg=ExternalMsg)]
impl Cw1Contract {
pub const fn new() -> Self {
Self
Expand Down Expand Up @@ -91,12 +96,11 @@ impl cosmwasm_std::CustomQuery for ExternalQuery {}

#[cfg(test)]
mod tests {
use crate::cw1::{InterfaceTypes, Querier as Cw1Querier};
use crate::{ExternalMsg, ExternalQuery};
use cosmwasm_std::{testing::mock_dependencies, Addr, CosmosMsg, Empty, QuerierWrapper};

use crate::{cw1::Querier, ExternalMsg, ExternalQuery};

use crate::cw1::InterfaceTypes;
use sylvia::types::InterfaceMessages;

#[test]
fn construct_messages() {
let contract = Addr::unchecked("contract");
Expand All @@ -110,9 +114,13 @@ mod tests {
let querier: QuerierWrapper<ExternalQuery> = QuerierWrapper::new(&deps.querier);

let cw1_querier = crate::cw1::BoundQuerier::borrowed(&contract, &querier);
let _: Result<ExternalQuery, _> = Querier::some_query(&cw1_querier, ExternalMsg {});
let _: Result<ExternalQuery, _> =
crate::cw1::Querier::some_query(&cw1_querier, ExternalMsg {});
let _: Result<ExternalQuery, _> = cw1_querier.some_query(ExternalMsg {});

let contract_querier = crate::cw1_contract::BoundQuerier::borrowed(&contract, &querier);
let _: Result<ExternalQuery, _> = contract_querier.some_query(ExternalMsg {});

// Construct messages with Interface extension
let _ =
<InterfaceTypes<ExternalMsg, _, ExternalQuery> as InterfaceMessages>::Query::some_query(
Expand Down

0 comments on commit 4e37906

Please sign in to comment.