frame_support_procedural/pallet/expand/
call.rs1use crate::{
19 pallet::{
20 expand::warnings::{weight_constant_warning, weight_witness_warning},
21 parse::{call::CallWeightDef, helper::CallReturnType},
22 Def,
23 },
24 COUNTER,
25};
26use proc_macro2::TokenStream as TokenStream2;
27use proc_macro_warning::Warning;
28use quote::{quote, ToTokens};
29use syn::spanned::Spanned;
30
31pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
35 let (span, where_clause, methods, docs) = match def.call.as_ref() {
36 Some(call) => {
37 let span = call.attr_span;
38 let where_clause = call.where_clause.clone();
39 let methods = call.methods.clone();
40 let docs = call.docs.clone();
41
42 (span, where_clause, methods, docs)
43 },
44 None => (def.item.span(), def.config.where_clause.clone(), Vec::new(), Vec::new()),
45 };
46 let frame_support = &def.frame_support;
47 let frame_system = &def.frame_system;
48 let type_impl_gen = &def.type_impl_generics(span);
49 let type_decl_bounded_gen = &def.type_decl_bounded_generics(span);
50 let type_use_gen = &def.type_use_generics(span);
51 let call_ident = syn::Ident::new("Call", span);
52 let pallet_ident = &def.pallet_struct.pallet;
53
54 let fn_name = methods.iter().map(|method| &method.name).collect::<Vec<_>>();
55 let call_index = methods.iter().map(|method| method.call_index).collect::<Vec<_>>();
56 let new_call_variant_fn_name = fn_name
57 .iter()
58 .map(|fn_name| quote::format_ident!("new_call_variant_{}", fn_name))
59 .collect::<Vec<_>>();
60
61 let new_call_variant_doc = fn_name
62 .iter()
63 .map(|fn_name| format!("Create a call with the variant `{}`.", fn_name))
64 .collect::<Vec<_>>();
65
66 let mut call_index_warnings = Vec::new();
67 for method in &methods {
69 if method.explicit_call_index || def.dev_mode {
70 continue
71 }
72
73 let warning = Warning::new_deprecated("ImplicitCallIndex")
74 .index(call_index_warnings.len())
75 .old("use implicit call indices")
76 .new("ensure that all calls have a `pallet::call_index` attribute or put the pallet into `dev` mode")
77 .help_links(&[
78 "https://github.com/paritytech/substrate/pull/12891",
79 "https://github.com/paritytech/substrate/pull/11381"
80 ])
81 .span(method.name.span())
82 .build_or_panic();
83 call_index_warnings.push(warning);
84 }
85
86 let mut fn_weight = Vec::<TokenStream2>::new();
87 let mut weight_warnings = Vec::new();
88 for method in &methods {
89 match &method.weight {
90 CallWeightDef::DevModeDefault => fn_weight.push(syn::parse_quote!(0)),
91 CallWeightDef::Immediate(e) => {
92 weight_constant_warning(e, def.dev_mode, &mut weight_warnings);
93 weight_witness_warning(method, def.dev_mode, &mut weight_warnings);
94
95 fn_weight.push(e.into_token_stream());
96 },
97 CallWeightDef::Inherited => {
98 let pallet_weight = def
99 .call
100 .as_ref()
101 .expect("we have methods; we have calls; qed")
102 .inherited_call_weight
103 .as_ref()
104 .expect("the parser prevents this");
105
106 let t = &pallet_weight.typename;
108 let n = &method.name;
109 fn_weight.push(quote!({ < #t > :: #n () }));
110 },
111 }
112 }
113 debug_assert_eq!(fn_weight.len(), methods.len());
114
115 let fn_doc = methods.iter().map(|method| &method.docs).collect::<Vec<_>>();
116
117 let args_name = methods
118 .iter()
119 .map(|method| method.args.iter().map(|(_, name, _)| name.clone()).collect::<Vec<_>>())
120 .collect::<Vec<_>>();
121
122 let args_name_stripped = methods
123 .iter()
124 .map(|method| {
125 method
126 .args
127 .iter()
128 .map(|(_, name, _)| {
129 syn::Ident::new(name.to_string().trim_start_matches('_'), name.span())
130 })
131 .collect::<Vec<_>>()
132 })
133 .collect::<Vec<_>>();
134
135 let make_args_name_pattern = |ref_tok| {
136 args_name
137 .iter()
138 .zip(args_name_stripped.iter())
139 .map(|(args_name, args_name_stripped)| {
140 args_name
141 .iter()
142 .zip(args_name_stripped)
143 .map(|(args_name, args_name_stripped)| {
144 if args_name == args_name_stripped {
145 quote::quote!( #ref_tok #args_name )
146 } else {
147 quote::quote!( #args_name_stripped: #ref_tok #args_name )
148 }
149 })
150 .collect::<Vec<_>>()
151 })
152 .collect::<Vec<_>>()
153 };
154
155 let args_name_pattern = make_args_name_pattern(None);
156 let args_name_pattern_ref = make_args_name_pattern(Some(quote::quote!(ref)));
157
158 let args_type = methods
159 .iter()
160 .map(|method| method.args.iter().map(|(_, _, type_)| type_.clone()).collect::<Vec<_>>())
161 .collect::<Vec<_>>();
162
163 let args_compact_attr = methods.iter().map(|method| {
164 method
165 .args
166 .iter()
167 .map(|(is_compact, _, type_)| {
168 if *is_compact {
169 quote::quote_spanned!(type_.span() => #[codec(compact)] )
170 } else {
171 quote::quote!()
172 }
173 })
174 .collect::<Vec<_>>()
175 });
176
177 let default_docs =
178 [syn::parse_quote!(r"Contains a variant per dispatchable extrinsic that this pallet has.")];
179 let docs = if docs.is_empty() { &default_docs[..] } else { &docs[..] };
180
181 let maybe_compile_error = if def.call.is_none() {
182 quote::quote! {
183 compile_error!(concat!(
184 "`",
185 stringify!($pallet_name),
186 "` does not have #[pallet::call] defined, perhaps you should remove `Call` from \
187 construct_runtime?",
188 ));
189 }
190 } else {
191 proc_macro2::TokenStream::new()
192 };
193
194 let count = COUNTER.with(|counter| counter.borrow_mut().inc());
195 let macro_ident = syn::Ident::new(&format!("__is_call_part_defined_{}", count), span);
196
197 let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" };
198
199 if let Some(call) = def.call.as_ref() {
201 let item_impl =
202 &mut def.item.content.as_mut().expect("Checked by def parser").1[call.index];
203 let syn::Item::Impl(item_impl) = item_impl else {
204 unreachable!("Checked by def parser");
205 };
206
207 item_impl.items.iter_mut().enumerate().for_each(|(i, item)| {
208 if let syn::ImplItem::Fn(method) = item {
209 let return_type =
210 &call.methods.get(i).expect("def should be consistent with item").return_type;
211
212 let (ok_type, err_type) = match return_type {
213 CallReturnType::DispatchResult => (
214 quote::quote!(()),
215 quote::quote!(#frame_support::pallet_prelude::DispatchError),
216 ),
217 CallReturnType::DispatchResultWithPostInfo => (
218 quote::quote!(#frame_support::dispatch::PostDispatchInfo),
219 quote::quote!(#frame_support::dispatch::DispatchErrorWithPostInfo),
220 ),
221 };
222
223 let block = &method.block;
224 method.block = syn::parse_quote! {{
225 #frame_support::storage::with_storage_layer::<#ok_type, #err_type, _>(
228 || #block
229 )
230 }};
231 }
232 });
233 }
234
235 let maybe_allow_attrs = methods
237 .iter()
238 .map(|method| {
239 method
240 .attrs
241 .iter()
242 .find(|attr| attr.path().is_ident("allow"))
243 .map_or(proc_macro2::TokenStream::new(), |attr| attr.to_token_stream())
244 })
245 .collect::<Vec<_>>();
246
247 let cfg_attrs = methods
248 .iter()
249 .map(|method| {
250 let attrs =
251 method.cfg_attrs.iter().map(|attr| attr.to_token_stream()).collect::<Vec<_>>();
252 quote::quote!( #( #attrs )* )
253 })
254 .collect::<Vec<_>>();
255
256 let feeless_check = methods.iter().map(|method| &method.feeless_check).collect::<Vec<_>>();
257 let feeless_check_result =
258 feeless_check.iter().zip(args_name.iter()).map(|(feeless_check, arg_name)| {
259 if let Some(feeless_check) = feeless_check {
260 quote::quote!(#feeless_check(origin, #( #arg_name, )*))
261 } else {
262 quote::quote!(false)
263 }
264 });
265
266 let deprecation = match crate::deprecation::get_deprecation_enum(
267 "e::quote! {#frame_support},
268 def.call.as_ref().map(|call| call.attrs.as_ref()).unwrap_or(&[]),
269 methods.iter().map(|item| (item.call_index as u8, item.attrs.as_ref())),
270 ) {
271 Ok(deprecation) => deprecation,
272 Err(e) => return e.into_compile_error(),
273 };
274
275 quote::quote_spanned!(span =>
276 #[doc(hidden)]
277 mod warnings {
278 #(
279 #call_index_warnings
280 )*
281 #(
282 #weight_warnings
283 )*
284 }
285
286 #[allow(unused_imports)]
287 #[doc(hidden)]
288 pub mod __substrate_call_check {
289 #[macro_export]
290 #[doc(hidden)]
291 macro_rules! #macro_ident {
292 ($pallet_name:ident) => {
293 #maybe_compile_error
294 };
295 }
296
297 #[doc(hidden)]
298 pub use #macro_ident as is_call_part_defined;
299 }
300
301 #( #[doc = #docs] )*
302 #[derive(
303 #frame_support::RuntimeDebugNoBound,
304 #frame_support::CloneNoBound,
305 #frame_support::EqNoBound,
306 #frame_support::PartialEqNoBound,
307 #frame_support::__private::codec::Encode,
308 #frame_support::__private::codec::Decode,
309 #frame_support::__private::scale_info::TypeInfo,
310 )]
311 #[codec(encode_bound())]
312 #[codec(decode_bound())]
313 #[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)]
314 #[allow(non_camel_case_types)]
315 pub enum #call_ident<#type_decl_bounded_gen> #where_clause {
316 #[doc(hidden)]
317 #[codec(skip)]
318 __Ignore(
319 ::core::marker::PhantomData<(#type_use_gen,)>,
320 #frame_support::Never,
321 ),
322 #(
323 #cfg_attrs
324 #( #[doc = #fn_doc] )*
325 #[codec(index = #call_index)]
326 #fn_name {
327 #(
328 #[allow(missing_docs)]
329 #args_compact_attr #args_name_stripped: #args_type
330 ),*
331 },
332 )*
333 }
334
335 impl<#type_impl_gen> #call_ident<#type_use_gen> #where_clause {
336 #(
337 #cfg_attrs
338 #[doc = #new_call_variant_doc]
339 pub fn #new_call_variant_fn_name(
340 #( #args_name_stripped: #args_type ),*
341 ) -> Self {
342 Self::#fn_name {
343 #( #args_name_stripped ),*
344 }
345 }
346 )*
347 }
348
349 impl<#type_impl_gen> #frame_support::dispatch::GetDispatchInfo
350 for #call_ident<#type_use_gen>
351 #where_clause
352 {
353 fn get_dispatch_info(&self) -> #frame_support::dispatch::DispatchInfo {
354 match *self {
355 #(
356 #cfg_attrs
357 Self::#fn_name { #( #args_name_pattern_ref, )* } => {
358 let __pallet_base_weight = #fn_weight;
359
360 let __pallet_weight = <
361 dyn #frame_support::dispatch::WeighData<( #( & #args_type, )* )>
362 >::weigh_data(&__pallet_base_weight, ( #( #args_name, )* ));
363
364 let __pallet_class = <
365 dyn #frame_support::dispatch::ClassifyDispatch<
366 ( #( & #args_type, )* )
367 >
368 >::classify_dispatch(&__pallet_base_weight, ( #( #args_name, )* ));
369
370 let __pallet_pays_fee = <
371 dyn #frame_support::dispatch::PaysFee<( #( & #args_type, )* )>
372 >::pays_fee(&__pallet_base_weight, ( #( #args_name, )* ));
373
374 #frame_support::dispatch::DispatchInfo {
375 weight: __pallet_weight,
376 class: __pallet_class,
377 pays_fee: __pallet_pays_fee,
378 }
379 },
380 )*
381 Self::__Ignore(_, _) => unreachable!("__Ignore cannot be used"),
382 }
383 }
384 }
385
386 impl<#type_impl_gen> #frame_support::dispatch::CheckIfFeeless for #call_ident<#type_use_gen>
387 #where_clause
388 {
389 type Origin = #frame_system::pallet_prelude::OriginFor<T>;
390 #[allow(unused_variables)]
391 fn is_feeless(&self, origin: &Self::Origin) -> bool {
392 match *self {
393 #(
394 #cfg_attrs
395 Self::#fn_name { #( #args_name_pattern_ref, )* } => {
396 #feeless_check_result
397 },
398 )*
399 Self::__Ignore(_, _) => unreachable!("__Ignore cannot be used"),
400 }
401 }
402 }
403
404 impl<#type_impl_gen> #frame_support::traits::GetCallName for #call_ident<#type_use_gen>
405 #where_clause
406 {
407 fn get_call_name(&self) -> &'static str {
408 match *self {
409 #( #cfg_attrs Self::#fn_name { .. } => stringify!(#fn_name), )*
410 Self::__Ignore(_, _) => unreachable!("__PhantomItem cannot be used."),
411 }
412 }
413
414 fn get_call_names() -> &'static [&'static str] {
415 &[ #( #cfg_attrs stringify!(#fn_name), )* ]
416 }
417 }
418
419 impl<#type_impl_gen> #frame_support::traits::GetCallIndex for #call_ident<#type_use_gen>
420 #where_clause
421 {
422 fn get_call_index(&self) -> u8 {
423 match *self {
424 #( #cfg_attrs Self::#fn_name { .. } => #call_index, )*
425 Self::__Ignore(_, _) => unreachable!("__PhantomItem cannot be used."),
426 }
427 }
428
429 fn get_call_indices() -> &'static [u8] {
430 &[ #( #cfg_attrs #call_index, )* ]
431 }
432 }
433
434 impl<#type_impl_gen> #frame_support::traits::UnfilteredDispatchable
435 for #call_ident<#type_use_gen>
436 #where_clause
437 {
438 type RuntimeOrigin = #frame_system::pallet_prelude::OriginFor<T>;
439 fn dispatch_bypass_filter(
440 self,
441 origin: Self::RuntimeOrigin
442 ) -> #frame_support::dispatch::DispatchResultWithPostInfo {
443 #frame_support::dispatch_context::run_in_context(|| {
444 match self {
445 #(
446 #cfg_attrs
447 Self::#fn_name { #( #args_name_pattern, )* } => {
448 #frame_support::__private::sp_tracing::enter_span!(
449 #frame_support::__private::sp_tracing::trace_span!(stringify!(#fn_name))
450 );
451 #maybe_allow_attrs
452 <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* )
453 .map(Into::into).map_err(Into::into)
454 },
455 )*
456 Self::__Ignore(_, _) => {
457 let _ = origin; unreachable!("__PhantomItem cannot be used.");
459 },
460 }
461 })
462 }
463 }
464
465 impl<#type_impl_gen> #frame_support::dispatch::Callable<T> for #pallet_ident<#type_use_gen>
466 #where_clause
467 {
468 type RuntimeCall = #call_ident<#type_use_gen>;
469 }
470
471 impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clause {
472 #[allow(dead_code)]
473 #[doc(hidden)]
474 pub fn call_functions() -> #frame_support::__private::metadata_ir::PalletCallMetadataIR {
475 #frame_support::__private::metadata_ir::PalletCallMetadataIR {
476 ty: #frame_support::__private::scale_info::meta_type::<#call_ident<#type_use_gen>>(),
477 deprecation_info: #deprecation,
478 }
479 }
480 }
481 )
482}