Skip to content

Commit aec2b10

Browse files
gui1117bkchr
andauthored
frame pallet macro: fix span for error on wrong returned type. (#5580)
Explicitly give the types in some generated code so that the error shows up good when user code is wrong. --------- Co-authored-by: Bastian Köcher <[email protected]>
1 parent f3f377f commit aec2b10

File tree

6 files changed

+125
-21
lines changed

6 files changed

+125
-21
lines changed

prdoc/pr_5580.prdoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
2+
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
3+
4+
title: Fix error message on pallet macro
5+
6+
doc:
7+
- audience: Runtime Dev
8+
description: |
9+
Improve error message for pallet macro generated code.
10+
11+
crates:
12+
- name: frame-support-procedural
13+
bump: patch

substrate/frame/support/procedural/src/pallet/expand/call.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use crate::{
1919
pallet::{
2020
expand::warnings::{weight_constant_warning, weight_witness_warning},
21-
parse::call::CallWeightDef,
21+
parse::{call::CallWeightDef, helper::CallReturnType},
2222
Def,
2323
},
2424
COUNTER,
@@ -197,18 +197,36 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
197197
let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" };
198198

199199
// Wrap all calls inside of storage layers
200-
if let Some(syn::Item::Impl(item_impl)) = def
201-
.call
202-
.as_ref()
203-
.map(|c| &mut def.item.content.as_mut().expect("Checked by def parser").1[c.index])
204-
{
205-
item_impl.items.iter_mut().for_each(|i| {
206-
if let syn::ImplItem::Fn(method) = i {
200+
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+
207223
let block = &method.block;
208224
method.block = syn::parse_quote! {{
209225
// We execute all dispatchable in a new storage layer, allowing them
210226
// to return an error at any point, and undoing any storage changes.
211-
#frame_support::storage::with_storage_layer(|| #block)
227+
#frame_support::storage::with_storage_layer::<#ok_type, #err_type, _>(
228+
|| #block
229+
)
212230
}};
213231
}
214232
});

substrate/frame/support/procedural/src/pallet/parse/call.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ pub struct CallVariantDef {
8989
pub cfg_attrs: Vec<syn::Attribute>,
9090
/// The optional `feeless_if` attribute on the `pallet::call`.
9191
pub feeless_check: Option<syn::ExprClosure>,
92+
/// The return type of the call: `DispatchInfo` or `DispatchResultWithPostInfo`.
93+
pub return_type: helper::CallReturnType,
9294
}
9395

9496
/// Attributes for functions in call impl block.
@@ -260,13 +262,7 @@ impl CallDef {
260262
},
261263
}
262264

263-
if let syn::ReturnType::Type(_, type_) = &method.sig.output {
264-
helper::check_pallet_call_return_type(type_)?;
265-
} else {
266-
let msg = "Invalid pallet::call, require return type \
267-
DispatchResultWithPostInfo";
268-
return Err(syn::Error::new(method.sig.span(), msg))
269-
}
265+
let return_type = helper::check_pallet_call_return_type(&method.sig)?;
270266

271267
let cfg_attrs: Vec<syn::Attribute> = helper::get_item_cfg_attrs(&method.attrs);
272268
let mut call_idx_attrs = vec![];
@@ -447,6 +443,7 @@ impl CallDef {
447443
attrs: method.attrs.clone(),
448444
cfg_attrs,
449445
feeless_check,
446+
return_type,
450447
});
451448
} else {
452449
let msg = "Invalid pallet::call, only method accepted";

substrate/frame/support/procedural/src/pallet/parse/helper.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -597,25 +597,38 @@ pub fn check_type_value_gen(
597597
Ok(i)
598598
}
599599

600+
/// The possible return type of a dispatchable.
601+
#[derive(Clone)]
602+
pub enum CallReturnType {
603+
DispatchResult,
604+
DispatchResultWithPostInfo,
605+
}
606+
600607
/// Check the keyword `DispatchResultWithPostInfo` or `DispatchResult`.
601-
pub fn check_pallet_call_return_type(type_: &syn::Type) -> syn::Result<()> {
602-
pub struct Checker;
608+
pub fn check_pallet_call_return_type(sig: &syn::Signature) -> syn::Result<CallReturnType> {
609+
let syn::ReturnType::Type(_, type_) = &sig.output else {
610+
let msg = "Invalid pallet::call, require return type \
611+
DispatchResultWithPostInfo";
612+
return Err(syn::Error::new(sig.span(), msg))
613+
};
614+
615+
pub struct Checker(CallReturnType);
603616
impl syn::parse::Parse for Checker {
604617
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
605618
let lookahead = input.lookahead1();
606619
if lookahead.peek(keyword::DispatchResultWithPostInfo) {
607620
input.parse::<keyword::DispatchResultWithPostInfo>()?;
608-
Ok(Self)
621+
Ok(Self(CallReturnType::DispatchResultWithPostInfo))
609622
} else if lookahead.peek(keyword::DispatchResult) {
610623
input.parse::<keyword::DispatchResult>()?;
611-
Ok(Self)
624+
Ok(Self(CallReturnType::DispatchResult))
612625
} else {
613626
Err(lookahead.error())
614627
}
615628
}
616629
}
617630

618-
syn::parse2::<Checker>(type_.to_token_stream()).map(|_| ())
631+
syn::parse2::<Checker>(type_.to_token_stream()).map(|c| c.0)
619632
}
620633

621634
pub(crate) fn two128_str(s: &str) -> TokenStream {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
#[frame_support::pallet(dev_mode)]
19+
mod pallet {
20+
use frame_support::pallet_prelude::*;
21+
use frame_system::pallet_prelude::*;
22+
23+
#[pallet::config]
24+
pub trait Config: frame_system::Config {}
25+
26+
#[pallet::pallet]
27+
pub struct Pallet<T>(_);
28+
29+
#[pallet::call]
30+
impl <T: Config> Pallet<T> {
31+
pub fn foo(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
32+
return Err(DispatchError::BadOrigin);
33+
}
34+
}
35+
}
36+
37+
fn main() {}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error[E0308]: mismatched types
2+
--> tests/pallet_ui/call_span_for_error.rs:32:15
3+
|
4+
32 | return Err(DispatchError::BadOrigin);
5+
| --- ^^^^^^^^^^^^^^^^^^^^^^^^ expected `DispatchErrorWithPostInfo<PostDispatchInfo>`, found `DispatchError`
6+
| |
7+
| arguments to this enum variant are incorrect
8+
|
9+
= note: expected struct `DispatchErrorWithPostInfo<PostDispatchInfo>`
10+
found enum `frame_support::pallet_prelude::DispatchError`
11+
help: the type constructed contains `frame_support::pallet_prelude::DispatchError` due to the type of the argument passed
12+
--> tests/pallet_ui/call_span_for_error.rs:32:11
13+
|
14+
32 | return Err(DispatchError::BadOrigin);
15+
| ^^^^------------------------^
16+
| |
17+
| this argument influences the type of `Err`
18+
note: tuple variant defined here
19+
--> $RUST/core/src/result.rs
20+
|
21+
| Err(#[stable(feature = "rust1", since = "1.0.0")] E),
22+
| ^^^
23+
help: call `Into::into` on this expression to convert `frame_support::pallet_prelude::DispatchError` into `DispatchErrorWithPostInfo<PostDispatchInfo>`
24+
|
25+
32 | return Err(DispatchError::BadOrigin.into());
26+
| +++++++

0 commit comments

Comments
 (0)