sylvia_derive/
interface.rs

1use communication::api::Api;
2use communication::enum_msg::EnumMessage;
3use communication::executor::Executor;
4use communication::querier::Querier;
5use mt::MtHelpers;
6use proc_macro2::TokenStream;
7use proc_macro_error::{emit_error, emit_warning};
8use quote::quote;
9use syn::{Ident, ItemTrait, TraitItem};
10
11use crate::parser::attributes::msg::MsgType;
12use crate::parser::variant_descs::AsVariantDescs;
13use crate::parser::{Custom, ParsedSylviaAttributes};
14use crate::types::associated_types::{AssociatedTypes, ItemType, EXEC_TYPE, QUERY_TYPE};
15use crate::types::msg_variant::MsgVariants;
16
17mod communication;
18mod mt;
19
20/// Preprocessed [`interface`](crate::interface) macro input.
21///
22/// Generates `sv` module containing:
23///     - [Messages](https://cosmwasm-docs.vercel.app/sylvia/macros/generated-types/message-types#interface-messages)
24///         - ExecMsg
25///         - QueryMsg
26///         - SudoMsg
27///     - [MultiTest](https://cosmwasm-docs.vercel.app/sylvia/macros/generated-types/multitest#proxy-trait) helpers
28///     - [Querier](https://cosmwasm-docs.vercel.app/cw-multi-test) trait implementation
29///     - [Executor](https://cosmwasm-docs.vercel.app/cw-multi-test) trait implementation
30///     - Api trait implementation
31pub struct InterfaceInput<'a> {
32    item: &'a ItemTrait,
33    custom: Custom,
34    associated_types: AssociatedTypes<'a>,
35}
36
37impl<'a> InterfaceInput<'a> {
38    pub fn new(item: &'a ItemTrait) -> Self {
39        if !item.generics.params.is_empty() {
40            emit_error!(
41                item.ident.span(), "Generics on traits are not supported. Use associated types instead.";
42                note = "Sylvia interfaces can be implemented only a single time per contract.";
43            );
44        }
45
46        if !item
47            .items
48            .iter()
49            .any(|item| matches!(item, TraitItem::Type(ty) if ty.ident == Ident::new("Error", ty.ident.span())))
50        {
51            emit_error!(
52                item.ident.span(), "Missing `Error` type defined for trait.";
53                note = "Error is an error type returned by generated types dispatch function. Messages handling function have to return an error type convertible to this Error type.";
54                note = "A trait error type should be bound to implement `From<cosmwasm_std::StdError>`.";
55            );
56        }
57
58        let custom = ParsedSylviaAttributes::new(item.attrs.iter())
59            .custom_attr
60            .unwrap_or_default();
61        let associated_types = AssociatedTypes::new(item);
62
63        if custom.msg.is_none()
64            && !associated_types
65                .as_names()
66                .any(|assoc_type| assoc_type == EXEC_TYPE)
67        {
68            emit_warning!(
69                item.ident.span(), "Missing both `{}` type and `#[sv::custom(msg=...)]` defined for the trait.", EXEC_TYPE;
70                note = "Implicitly it means that the trait could not be implemented for contracts that use CustomMsg different than `cosmwasm_std::Empty`";
71                note = "If this behaviour is intended, please add `#[sv::custom(msg=sylvia::cw_std::Empty]` attribute.";
72            );
73        }
74
75        if custom.query.is_none()
76            && !associated_types
77                .as_names()
78                .any(|assoc_type| assoc_type == QUERY_TYPE)
79        {
80            emit_warning!(
81                item.ident.span(), "Missing both `{}` type and `#[sv::custom(query=...)]` defined for the trait.", QUERY_TYPE;
82                note = "Implicitly it means that the trait could not be implemented for contracts that use CustomQuery different than `cosmwasm_std::Empty`";
83                note = "If this behaviour is intended, please add `#[sv::custom(query=sylvia::cw_std::Empty]` attribute.";
84            );
85        }
86
87        Self {
88            item,
89            custom,
90            associated_types,
91        }
92    }
93
94    /// Processes the input and generates the interface code.
95    pub fn process(&self) -> TokenStream {
96        let Self {
97            associated_types,
98            item,
99            custom,
100        } = self;
101        let messages = self.emit_messages();
102        let associated_names: Vec<_> = associated_types
103            .without_error()
104            .map(ItemType::as_name)
105            .collect();
106
107        let executor_variants =
108            MsgVariants::new(item.as_variants(), MsgType::Exec, &associated_names, &None);
109        let query_variants =
110            MsgVariants::new(item.as_variants(), MsgType::Query, &associated_names, &None);
111        let executor =
112            Executor::new(&executor_variants, associated_types, &item.ident).emit_executor_trait();
113        let querier =
114            Querier::new(&query_variants, associated_types, &item.ident).emit_querier_trait();
115
116        let interface_messages = Api::new(item, custom, associated_types).emit();
117
118        let multitest_helpers = self.emit_multitest_helpers();
119
120        quote! {
121            pub mod sv {
122                use super::*;
123                #messages
124
125                #querier
126
127                #executor
128
129                #interface_messages
130
131                #multitest_helpers
132            }
133        }
134    }
135
136    fn emit_messages(&self) -> TokenStream {
137        let exec = self.emit_msg(MsgType::Exec);
138        let query = self.emit_msg(MsgType::Query);
139        let sudo = self.emit_msg(MsgType::Sudo);
140
141        let instantiate = MsgVariants::new(
142            self.item.as_variants(),
143            MsgType::Instantiate,
144            &[] as &[&Ident],
145            &None,
146        );
147
148        if let Some(msg_variant) = instantiate.variants().next() {
149            emit_error!(
150                msg_variant.name().span(), "The message attribute `instantiate` is not supported in interfaces.";
151                note = "Contracts need to implement `instantiate` method within their `impl` block.";
152            );
153        }
154
155        let migrate = MsgVariants::new(
156            self.item.as_variants(),
157            MsgType::Migrate,
158            &[] as &[&Ident],
159            &None,
160        );
161
162        if let Some(msg_variant) = migrate.variants().next() {
163            emit_error!(
164                msg_variant.name().span(), "The message attribute `migrate` is not supported in interfaces";
165                note = "Contracts need to implement `migrate` method within their `impl` block.";
166            );
167        }
168
169        quote! {
170            #exec
171
172            #query
173
174            #sudo
175        }
176    }
177
178    fn emit_msg(&self, msg_ty: MsgType) -> TokenStream {
179        let where_clause = &self.associated_types.as_where_clause();
180        let associated_names: Vec<_> = self
181            .associated_types
182            .without_error()
183            .map(ItemType::as_name)
184            .collect();
185        let variants = MsgVariants::new(
186            self.item.as_variants(),
187            msg_ty,
188            &associated_names,
189            where_clause,
190        );
191
192        EnumMessage::new(
193            self.item,
194            msg_ty,
195            &self.custom,
196            variants,
197            &self.associated_types,
198        )
199        .emit()
200    }
201
202    fn emit_multitest_helpers(&self) -> TokenStream {
203        if !cfg!(feature = "mt") {
204            return quote! {};
205        }
206
207        let Self { item, .. } = self;
208        let associated_types = &self.associated_types;
209
210        MtHelpers::new(item, associated_types).emit()
211    }
212}