sylvia_derive/
interface.rs1use 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
20pub 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 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}