1use super::{helper, InheritedCallWeightAttr};
19use frame_support_procedural_tools::get_doc_literals;
20use proc_macro2::Span;
21use quote::ToTokens;
22use std::collections::HashMap;
23use syn::{spanned::Spanned, ExprClosure};
24
25mod keyword {
27 syn::custom_keyword!(Call);
28 syn::custom_keyword!(OriginFor);
29 syn::custom_keyword!(RuntimeOrigin);
30 syn::custom_keyword!(weight);
31 syn::custom_keyword!(call_index);
32 syn::custom_keyword!(compact);
33 syn::custom_keyword!(T);
34 syn::custom_keyword!(pallet);
35 syn::custom_keyword!(feeless_if);
36}
37
38pub struct CallDef {
40 pub where_clause: Option<syn::WhereClause>,
42 pub instances: Vec<helper::InstanceUsage>,
44 pub index: usize,
46 pub methods: Vec<CallVariantDef>,
48 pub attr_span: proc_macro2::Span,
50 pub docs: Vec<syn::Expr>,
52 pub inherited_call_weight: Option<InheritedCallWeightAttr>,
54 pub attrs: Vec<syn::Attribute>,
56}
57
58#[derive(Clone)]
60pub enum CallWeightDef {
61 Immediate(syn::Expr),
63
64 DevModeDefault,
66
67 Inherited,
71}
72
73#[derive(Clone)]
75pub struct CallVariantDef {
76 pub name: syn::Ident,
78 pub args: Vec<(bool, syn::Ident, Box<syn::Type>)>,
80 pub weight: CallWeightDef,
82 pub call_index: u8,
84 pub explicit_call_index: bool,
86 pub docs: Vec<syn::Expr>,
88 pub attrs: Vec<syn::Attribute>,
90 pub cfg_attrs: Vec<syn::Attribute>,
92 pub feeless_check: Option<syn::ExprClosure>,
94 pub return_type: helper::CallReturnType,
96}
97
98pub enum FunctionAttr {
100 CallIndex(u8),
102 Weight(syn::Expr),
104 FeelessIf(Span, syn::ExprClosure),
106}
107
108impl syn::parse::Parse for FunctionAttr {
109 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
110 input.parse::<syn::Token![#]>()?;
111 let content;
112 syn::bracketed!(content in input);
113 content.parse::<keyword::pallet>()?;
114 content.parse::<syn::Token![::]>()?;
115
116 let lookahead = content.lookahead1();
117 if lookahead.peek(keyword::weight) {
118 content.parse::<keyword::weight>()?;
119 let weight_content;
120 syn::parenthesized!(weight_content in content);
121 Ok(FunctionAttr::Weight(weight_content.parse::<syn::Expr>()?))
122 } else if lookahead.peek(keyword::call_index) {
123 content.parse::<keyword::call_index>()?;
124 let call_index_content;
125 syn::parenthesized!(call_index_content in content);
126 let index = call_index_content.parse::<syn::LitInt>()?;
127 if !index.suffix().is_empty() {
128 let msg = "Number literal must not have a suffix";
129 return Err(syn::Error::new(index.span(), msg));
130 }
131 Ok(FunctionAttr::CallIndex(index.base10_parse()?))
132 } else if lookahead.peek(keyword::feeless_if) {
133 content.parse::<keyword::feeless_if>()?;
134 let closure_content;
135 syn::parenthesized!(closure_content in content);
136 Ok(FunctionAttr::FeelessIf(
137 closure_content.span(),
138 closure_content.parse::<syn::ExprClosure>().map_err(|e| {
139 let msg = "Invalid feeless_if attribute: expected a closure";
140 let mut err = syn::Error::new(closure_content.span(), msg);
141 err.combine(e);
142 err
143 })?,
144 ))
145 } else {
146 Err(lookahead.error())
147 }
148 }
149}
150
151pub struct ArgAttrIsCompact;
154
155impl syn::parse::Parse for ArgAttrIsCompact {
156 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
157 input.parse::<syn::Token![#]>()?;
158 let content;
159 syn::bracketed!(content in input);
160 content.parse::<keyword::pallet>()?;
161 content.parse::<syn::Token![::]>()?;
162
163 content.parse::<keyword::compact>()?;
164 Ok(ArgAttrIsCompact)
165 }
166}
167
168pub fn check_dispatchable_first_arg_type(ty: &syn::Type, is_ref: bool) -> syn::Result<()> {
170 pub struct CheckOriginFor(bool);
171 impl syn::parse::Parse for CheckOriginFor {
172 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
173 let is_ref = input.parse::<syn::Token![&]>().is_ok();
174 input.parse::<keyword::OriginFor>()?;
175 input.parse::<syn::Token![<]>()?;
176 input.parse::<keyword::T>()?;
177 input.parse::<syn::Token![>]>()?;
178
179 Ok(Self(is_ref))
180 }
181 }
182
183 pub struct CheckRuntimeOrigin;
184 impl syn::parse::Parse for CheckRuntimeOrigin {
185 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
186 input.parse::<keyword::T>()?;
187 input.parse::<syn::Token![::]>()?;
188 input.parse::<keyword::RuntimeOrigin>()?;
189
190 Ok(Self)
191 }
192 }
193
194 let result_origin_for = syn::parse2::<CheckOriginFor>(ty.to_token_stream());
195 let result_runtime_origin = syn::parse2::<CheckRuntimeOrigin>(ty.to_token_stream());
196 return match (result_origin_for, result_runtime_origin) {
197 (Ok(CheckOriginFor(has_ref)), _) if is_ref == has_ref => Ok(()),
198 (_, Ok(_)) => Ok(()),
199 (_, _) => {
200 let msg = if is_ref {
201 "Invalid type: expected `&OriginFor<T>`"
202 } else {
203 "Invalid type: expected `OriginFor<T>` or `T::RuntimeOrigin`"
204 };
205 return Err(syn::Error::new(ty.span(), msg));
206 },
207 };
208}
209
210impl CallDef {
211 pub fn try_from(
212 attr_span: proc_macro2::Span,
213 index: usize,
214 item: &mut syn::Item,
215 dev_mode: bool,
216 inherited_call_weight: Option<InheritedCallWeightAttr>,
217 ) -> syn::Result<Self> {
218 let item_impl = if let syn::Item::Impl(item) = item {
219 item
220 } else {
221 return Err(syn::Error::new(item.span(), "Invalid pallet::call, expected item impl"));
222 };
223 let instances = vec![
224 helper::check_impl_gen(&item_impl.generics, item_impl.impl_token.span())?,
225 helper::check_pallet_struct_usage(&item_impl.self_ty)?,
226 ];
227
228 if let Some((_, _, for_)) = item_impl.trait_ {
229 let msg = "Invalid pallet::call, expected no trait ident as in \
230 `impl<..> Pallet<..> { .. }`";
231 return Err(syn::Error::new(for_.span(), msg));
232 }
233
234 let mut methods = vec![];
235 let mut indices = HashMap::new();
236 let mut last_index: Option<u8> = None;
237 for item in &mut item_impl.items {
238 if let syn::ImplItem::Fn(method) = item {
239 if !matches!(method.vis, syn::Visibility::Public(_)) {
240 let msg = "Invalid pallet::call, dispatchable function must be public: \
241 `pub fn`";
242
243 let span = match method.vis {
244 syn::Visibility::Inherited => method.sig.span(),
245 _ => method.vis.span(),
246 };
247
248 return Err(syn::Error::new(span, msg));
249 }
250
251 match method.sig.inputs.first() {
252 None => {
253 let msg = "Invalid pallet::call, must have at least origin arg";
254 return Err(syn::Error::new(method.sig.span(), msg));
255 },
256 Some(syn::FnArg::Receiver(_)) => {
257 let msg = "Invalid pallet::call, first argument must be a typed argument, \
258 e.g. `origin: OriginFor<T>`";
259 return Err(syn::Error::new(method.sig.span(), msg));
260 },
261 Some(syn::FnArg::Typed(arg)) => {
262 check_dispatchable_first_arg_type(&arg.ty, false)?;
263 },
264 }
265
266 let return_type = helper::check_pallet_call_return_type(&method.sig)?;
267
268 let cfg_attrs: Vec<syn::Attribute> = helper::get_item_cfg_attrs(&method.attrs);
269 let mut call_idx_attrs = vec![];
270 let mut weight_attrs = vec![];
271 let mut feeless_attrs = vec![];
272 for attr in helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter() {
273 match attr {
274 FunctionAttr::CallIndex(_) => {
275 call_idx_attrs.push(attr);
276 },
277 FunctionAttr::Weight(_) => {
278 weight_attrs.push(attr);
279 },
280 FunctionAttr::FeelessIf(span, _) => {
281 feeless_attrs.push((span, attr));
282 },
283 }
284 }
285
286 if weight_attrs.is_empty() && dev_mode {
287 let empty_weight: syn::Expr = syn::parse_quote!(0);
290 weight_attrs.push(FunctionAttr::Weight(empty_weight));
291 }
292
293 let weight = match weight_attrs.len() {
294 0 if inherited_call_weight.is_some() => CallWeightDef::Inherited,
295 0 if dev_mode => CallWeightDef::DevModeDefault,
296 0 => return Err(syn::Error::new(
297 method.sig.span(),
298 "A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an
299 inherited weight from the `#[pallet:call(weight($type))]` attribute, but
300 none were given.",
301 )),
302 1 => match weight_attrs.pop().unwrap() {
303 FunctionAttr::Weight(w) => CallWeightDef::Immediate(w),
304 _ => unreachable!("checked during creation of the let binding"),
305 },
306 _ => {
307 let msg = "Invalid pallet::call, too many weight attributes given";
308 return Err(syn::Error::new(method.sig.span(), msg));
309 },
310 };
311
312 if call_idx_attrs.len() > 1 {
313 let msg = "Invalid pallet::call, too many call_index attributes given";
314 return Err(syn::Error::new(method.sig.span(), msg));
315 }
316 let call_index = call_idx_attrs.pop().map(|attr| match attr {
317 FunctionAttr::CallIndex(idx) => idx,
318 _ => unreachable!("checked during creation of the let binding"),
319 });
320 let explicit_call_index = call_index.is_some();
321
322 let final_index = match call_index {
323 Some(i) => i,
324 None =>
325 last_index.map_or(Some(0), |idx| idx.checked_add(1)).ok_or_else(|| {
326 let msg = "Call index doesn't fit into u8, index is 256";
327 syn::Error::new(method.sig.span(), msg)
328 })?,
329 };
330 last_index = Some(final_index);
331
332 if let Some(used_fn) = indices.insert(final_index, method.sig.ident.clone()) {
333 let msg = format!(
334 "Call indices are conflicting: Both functions {} and {} are at index {}",
335 used_fn, method.sig.ident, final_index,
336 );
337 let mut err = syn::Error::new(used_fn.span(), &msg);
338 err.combine(syn::Error::new(method.sig.ident.span(), msg));
339 return Err(err);
340 }
341
342 let mut args = vec![];
343 for arg in method.sig.inputs.iter_mut().skip(1) {
344 let arg = if let syn::FnArg::Typed(arg) = arg {
345 arg
346 } else {
347 unreachable!("Only first argument can be receiver");
348 };
349
350 let arg_attrs: Vec<ArgAttrIsCompact> =
351 helper::take_item_pallet_attrs(&mut arg.attrs)?;
352
353 if arg_attrs.len() > 1 {
354 let msg = "Invalid pallet::call, argument has too many attributes";
355 return Err(syn::Error::new(arg.span(), msg));
356 }
357
358 let arg_ident = if let syn::Pat::Ident(pat) = &*arg.pat {
359 pat.ident.clone()
360 } else {
361 let msg = "Invalid pallet::call, argument must be ident";
362 return Err(syn::Error::new(arg.pat.span(), msg));
363 };
364
365 args.push((!arg_attrs.is_empty(), arg_ident, arg.ty.clone()));
366 }
367
368 let docs = get_doc_literals(&method.attrs);
369
370 if feeless_attrs.len() > 1 {
371 let msg = "Invalid pallet::call, there can only be one feeless_if attribute";
372 return Err(syn::Error::new(feeless_attrs[1].0, msg));
373 }
374 let feeless_check: Option<ExprClosure> =
375 feeless_attrs.pop().map(|(_, attr)| match attr {
376 FunctionAttr::FeelessIf(_, closure) => closure,
377 _ => unreachable!("checked during creation of the let binding"),
378 });
379
380 if let Some(ref feeless_check) = feeless_check {
381 if feeless_check.inputs.len() != args.len() + 1 {
382 let msg = "Invalid pallet::call, feeless_if closure must have same \
383 number of arguments as the dispatchable function";
384 return Err(syn::Error::new(feeless_check.span(), msg));
385 }
386
387 match feeless_check.inputs.first() {
388 None => {
389 let msg = "Invalid pallet::call, feeless_if closure must have at least origin arg";
390 return Err(syn::Error::new(feeless_check.span(), msg));
391 },
392 Some(syn::Pat::Type(arg)) => {
393 check_dispatchable_first_arg_type(&arg.ty, true)?;
394 },
395 _ => {
396 let msg = "Invalid pallet::call, feeless_if closure first argument must be a typed argument, \
397 e.g. `origin: OriginFor<T>`";
398 return Err(syn::Error::new(feeless_check.span(), msg));
399 },
400 }
401
402 for (feeless_arg, arg) in feeless_check.inputs.iter().skip(1).zip(args.iter()) {
403 let feeless_arg_type = if let syn::Pat::Type(syn::PatType { ty, .. }) =
404 feeless_arg.clone()
405 {
406 if let syn::Type::Reference(pat) = *ty {
407 pat.elem.clone()
408 } else {
409 let msg = "Invalid pallet::call, feeless_if closure argument must be a reference";
410 return Err(syn::Error::new(ty.span(), msg));
411 }
412 } else {
413 let msg = "Invalid pallet::call, feeless_if closure argument must be a type ascription pattern";
414 return Err(syn::Error::new(feeless_arg.span(), msg));
415 };
416
417 if feeless_arg_type != arg.2 {
418 let msg =
419 "Invalid pallet::call, feeless_if closure argument must have \
420 a reference to the same type as the dispatchable function argument";
421 return Err(syn::Error::new(feeless_arg.span(), msg));
422 }
423 }
424
425 let valid_return = match &feeless_check.output {
426 syn::ReturnType::Type(_, type_) => match *(type_.clone()) {
427 syn::Type::Path(syn::TypePath { path, .. }) => path.is_ident("bool"),
428 _ => false,
429 },
430 _ => false,
431 };
432 if !valid_return {
433 let msg = "Invalid pallet::call, feeless_if closure must return `bool`";
434 return Err(syn::Error::new(feeless_check.output.span(), msg));
435 }
436 }
437
438 methods.push(CallVariantDef {
439 name: method.sig.ident.clone(),
440 weight,
441 call_index: final_index,
442 explicit_call_index,
443 args,
444 docs,
445 attrs: method.attrs.clone(),
446 cfg_attrs,
447 feeless_check,
448 return_type,
449 });
450 } else {
451 let msg = "Invalid pallet::call, only method accepted";
452 return Err(syn::Error::new(item.span(), msg));
453 }
454 }
455
456 Ok(Self {
457 index,
458 attr_span,
459 instances,
460 methods,
461 where_clause: item_impl.generics.where_clause.clone(),
462 docs: get_doc_literals(&item_impl.attrs),
463 inherited_call_weight,
464 attrs: item_impl.attrs.clone(),
465 })
466 }
467}