sylvia_derive/
entry_points.rs

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
17/// Preprocessed [`entry_points`](crate::entry_points) macro input.
18///
19/// Generates `entry_points` module containing:
20///     - instantiate, execute, query and sudo entry points by default
21///     - migrate and reply entry points if respective messages are defined
22pub 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    /// Process the input and generate the entry points code.
52    pub fn process(&self) -> TokenStream {
53        let Self { item, args } = self;
54
55        EntryPoints::new(item, args).emit()
56    }
57}
58
59/// Defines logic for generating entry points.
60///
61/// By default generates entry points for `instantiate`, `execute`, `query` and `sudo` messages.
62/// Generates `reply` and `migrate` entry points if respective messages are defined.
63pub 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}