1use proc_macro2::{Span, TokenStream};
2use proc_macro_error::emit_error;
3use quote::quote;
4use syn::fold::Fold;
5use syn::{parse_quote, GenericParam, Ident, ItemImpl, Type, WhereClause};
6
7use crate::crate_module;
8use crate::fold::StripGenerics;
9use crate::parser::attributes::features::SylviaFeatures;
10use crate::parser::attributes::msg::MsgType;
11use crate::parser::variant_descs::AsVariantDescs;
12use crate::parser::{
13 EntryPointArgs, FilteredOverrideEntryPoints, OverrideEntryPoint, ParsedSylviaAttributes,
14};
15use crate::types::msg_variant::MsgVariants;
16
17pub struct EntryPointInput<'a> {
23 item: &'a ItemImpl,
24 args: EntryPointArgs,
25}
26
27impl<'a> EntryPointInput<'a> {
28 pub fn new(item: &'a ItemImpl, args: EntryPointArgs, attr_span: Span) -> Self {
29 let instantiate =
30 MsgVariants::<GenericParam>::new(item.as_variants(), MsgType::Instantiate, &[], &None);
31
32 if args.generics.len() != item.generics.params.len() {
33 emit_error!(
34 attr_span,
35 "Missing concrete types.";
36 note = "For every generic type in the contract, a concrete type must be provided in `#[entry_points(generics<T1, T2, ...>)]`.";
37 );
38 }
39
40 if instantiate.get_only_variant().is_none() {
41 emit_error!(
42 attr_span, "Missing instantiation message.";
43 note = "`sylvia::entry_points` requires exactly one method marked with `#[sv::msg(instantiation)]` attribute.";
44 note = "Make sure you implemented the `entry_points` macro above the `contract` macro."
45 );
46 }
47
48 Self { item, args }
49 }
50
51 pub fn process(&self) -> TokenStream {
53 let Self { item, args } = self;
54
55 EntryPoints::new(item, args).emit()
56 }
57}
58
59pub struct EntryPoints<'a> {
64 source: &'a ItemImpl,
65 name: Type,
66 error: Type,
67 reply: Option<Ident>,
68 override_entry_points: Vec<OverrideEntryPoint>,
69 generics: Vec<&'a GenericParam>,
70 where_clause: &'a Option<WhereClause>,
71 attrs: &'a EntryPointArgs,
72 sv_features: SylviaFeatures,
73}
74
75impl<'a> EntryPoints<'a> {
76 pub fn new(source: &'a ItemImpl, attrs: &'a EntryPointArgs) -> Self {
77 let name = StripGenerics.fold_type(*source.self_ty.clone());
78 let parsed_attrs = ParsedSylviaAttributes::new(source.attrs.iter());
79 let override_entry_points = parsed_attrs.override_entry_point_attrs;
80 let sv_features = parsed_attrs.sv_features;
81
82 let error = parsed_attrs.error_attrs.unwrap_or_default().error;
83
84 let generics: Vec<_> = source.generics.params.iter().collect();
85 let where_clause = &source.generics.where_clause;
86
87 let reply =
88 MsgVariants::<GenericParam>::new(source.as_variants(), MsgType::Reply, &[], &None)
89 .variants()
90 .map(|variant| variant.function_name().clone())
91 .next();
92
93 Self {
94 source,
95 name,
96 error,
97 reply,
98 override_entry_points,
99 generics,
100 where_clause,
101 attrs,
102 sv_features,
103 }
104 }
105
106 pub fn emit(&self) -> TokenStream {
107 let Self {
108 source,
109 reply,
110 override_entry_points,
111 generics,
112 where_clause,
113 ..
114 } = self;
115
116 let entry_points = [
117 MsgType::Instantiate,
118 MsgType::Exec,
119 MsgType::Query,
120 MsgType::Sudo,
121 ]
122 .into_iter()
123 .map(
124 |msg_ty| match override_entry_points.get_entry_point(msg_ty) {
125 Some(_) => quote! {},
126 None => self.emit_default_entry_point(msg_ty),
127 },
128 );
129
130 let is_migrate = MsgVariants::new(
131 source.as_variants(),
132 MsgType::Migrate,
133 generics,
134 where_clause,
135 )
136 .get_only_variant()
137 .is_some();
138
139 let migrate_not_overridden = override_entry_points
140 .get_entry_point(MsgType::Migrate)
141 .is_none();
142
143 let migrate = if migrate_not_overridden && is_migrate {
144 self.emit_default_entry_point(MsgType::Migrate)
145 } else {
146 quote! {}
147 };
148
149 let reply_ep = override_entry_points
150 .get_entry_point(MsgType::Reply)
151 .map(|_| quote! {})
152 .unwrap_or_else(|| {
153 if reply.is_some() {
154 self.emit_default_entry_point(MsgType::Reply)
155 } else {
156 quote! {}
157 }
158 });
159
160 quote! {
161 pub mod entry_points {
162 use super::*;
163
164 #(#entry_points)*
165
166 #migrate
167
168 #reply_ep
169 }
170 }
171 }
172
173 fn emit_default_entry_point(&self, msg_ty: MsgType) -> TokenStream {
174 let Self {
175 name,
176 error,
177 attrs,
178 reply,
179 sv_features,
180 ..
181 } = self;
182 let sylvia = crate_module();
183
184 let attr_generics = &attrs.generics;
185 let (contract, contract_turbofish) = if attr_generics.is_empty() {
186 (quote! { #name }, quote! { #name })
187 } else {
188 (
189 quote! { #name < #attr_generics > },
190 quote! { #name :: < #attr_generics > },
191 )
192 };
193
194 let custom_msg: Type =
195 parse_quote! { < #contract as #sylvia ::types::ContractApi > :: CustomMsg };
196 let custom_query: Type =
197 parse_quote! { < #contract as #sylvia ::types::ContractApi > :: CustomQuery };
198
199 let result = msg_ty.emit_result_type(&custom_msg, error);
200 let params = msg_ty.emit_ctx_params(&custom_query);
201 let values = msg_ty.emit_ctx_values();
202 let ep_name = msg_ty.emit_ep_name();
203 let associated_name = msg_ty.as_accessor_wrapper_name();
204 let msg = match msg_ty {
205 MsgType::Reply => quote! { msg: #sylvia ::cw_std::Reply },
206 _ => quote! { msg: < #contract as #sylvia ::types::ContractApi> :: #associated_name },
207 };
208 let dispatch = match msg_ty {
209 MsgType::Reply if sv_features.replies => quote! {
210 let contract = #contract_turbofish ::new();
211 sv::dispatch_reply(deps, env, msg, contract).map_err(Into::into)
212 },
213 MsgType::Reply => quote! {
214 #contract_turbofish ::new(). #reply((deps, env).into(), msg).map_err(Into::into)
215 },
216 _ => quote! {
217 msg.dispatch(& #contract_turbofish ::new() , ( #values )).map_err(Into::into)
218 },
219 };
220
221 let cw_std = quote! { #sylvia ::cw_std }.to_string();
222
223 quote! {
224 #[#sylvia ::cw_std::entry_point(crate = #cw_std )]
225 pub fn #ep_name (
226 #params ,
227 #msg
228 ) -> #result {
229 #dispatch
230 }
231 }
232 }
233}