diff --git a/frame/support/procedural/src/pallet/parse/config.rs b/frame/support/procedural/src/pallet/parse/config.rs index 79d4680752b90..69dfaeb7f9e9b 100644 --- a/frame/support/procedural/src/pallet/parse/config.rs +++ b/frame/support/procedural/src/pallet/parse/config.rs @@ -16,6 +16,7 @@ // limitations under the License. use super::helper; +use core::convert::TryFrom; use syn::spanned::Spanned; use quote::ToTokens; @@ -25,7 +26,6 @@ mod keyword { syn::custom_keyword!(From); syn::custom_keyword!(T); syn::custom_keyword!(I); - syn::custom_keyword!(Get); syn::custom_keyword!(config); syn::custom_keyword!(IsType); syn::custom_keyword!(Event); @@ -62,19 +62,41 @@ pub struct ConstMetadataDef { pub doc: Vec, } -impl syn::parse::Parse for ConstMetadataDef { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let doc = helper::get_doc_literals(&syn::Attribute::parse_outer(input)?); - input.parse::()?; - let ident = input.parse::()?; - input.parse::()?; - input.parse::()?; - input.parse::()?; - let mut type_ = input.parse::()?; - type_ = syn::parse2::(replace_self_by_t(type_.to_token_stream())) +impl TryFrom<&syn::TraitItemType> for ConstMetadataDef { + type Error = syn::Error; + + fn try_from(trait_ty: &syn::TraitItemType) -> Result { + let err = |span, msg| + syn::Error::new(span, format!("Invalid usage of `#[pallet::constant]`: {}", msg)); + let doc = helper::get_doc_literals(&trait_ty.attrs); + let ident = trait_ty.ident.clone(); + let bound = trait_ty.bounds + .iter() + .find_map(|b| + if let syn::TypeParamBound::Trait(tb) = b { + tb.path.segments + .last() + .and_then(|s| if s.ident == "Get" { Some(s) } else { None } ) + } else { + None + } + ) + .ok_or_else(|| err(trait_ty.span(), "`Get` trait bound not found"))?; + let type_arg = if let syn::PathArguments::AngleBracketed (ref ab) = bound.arguments { + if ab.args.len() == 1 { + if let syn::GenericArgument::Type(ref ty) = ab.args[0] { + Ok(ty) + } else { + Err(err(ab.args[0].span(), "Expected a type argument")) + } + } else { + Err(err(bound.span(), "Expected a single type argument")) + } + } else { + Err(err(bound.span(), "Expected trait generic args")) + }?; + let type_ = syn::parse2::(replace_self_by_t(type_arg.to_token_stream())) .expect("Internal error: replacing `Self` by `T` should result in valid type"); - input.parse::]>()?; - input.parse::()?; Ok(Self { ident, type_, doc }) } @@ -322,16 +344,8 @@ impl ConfigDef { if type_attrs_const.len() == 1 { match trait_item { - syn::TraitItem::Type(type_) => { - let constant = syn::parse2::(type_.to_token_stream()) - .map_err(|e| { - let error_msg = "Invalid usage of `#[pallet::constant]`, syntax \ - must be `type $SomeIdent: Get<$SomeType>;`"; - let mut err = syn::Error::new(type_.span(), error_msg); - err.combine(e); - err - })?; - + syn::TraitItem::Type(ref type_) => { + let constant = ConstMetadataDef::try_from(type_)?; consts_metadata.push(constant); }, _ => { diff --git a/frame/support/test/tests/pallet_ui.rs b/frame/support/test/tests/pallet_ui.rs index 1836b06cabfdd..e5f4a54dfb000 100644 --- a/frame/support/test/tests/pallet_ui.rs +++ b/frame/support/test/tests/pallet_ui.rs @@ -23,4 +23,5 @@ fn pallet_ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/pallet_ui/*.rs"); + t.pass("tests/pallet_ui/pass/*.rs"); } diff --git a/frame/support/test/tests/pallet_ui/pass/trait_constant_valid_bounds.rs b/frame/support/test/tests/pallet_ui/pass/trait_constant_valid_bounds.rs new file mode 100644 index 0000000000000..71eb4f2992b39 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/trait_constant_valid_bounds.rs @@ -0,0 +1,29 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + type U: Get; + + #[pallet::constant] + type V: Get + From; + + #[pallet::constant] + type W: From + Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound.stderr b/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound.stderr index 16c3531140eaa..057ec6ffb2c75 100644 --- a/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound.stderr +++ b/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound.stderr @@ -1,11 +1,5 @@ -error: Invalid usage of `#[pallet::constant]`, syntax must be `type $SomeIdent: Get<$SomeType>;` +error: Invalid usage of `#[pallet::constant]`: `Get` trait bound not found --> $DIR/trait_constant_invalid_bound.rs:9:3 | 9 | type U; | ^^^^ - -error: expected `:` - --> $DIR/trait_constant_invalid_bound.rs:9:9 - | -9 | type U; - | ^ diff --git a/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound_lifetime.rs b/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound_lifetime.rs new file mode 100644 index 0000000000000..47303f2b20a02 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound_lifetime.rs @@ -0,0 +1,23 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::Hooks; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + type U: Get<'static>; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound_lifetime.stderr b/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound_lifetime.stderr new file mode 100644 index 0000000000000..8d830fed8f392 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/trait_constant_invalid_bound_lifetime.stderr @@ -0,0 +1,5 @@ +error: Invalid usage of `#[pallet::constant]`: Expected a type argument + --> $DIR/trait_constant_invalid_bound_lifetime.rs:9:15 + | +9 | type U: Get<'static>; + | ^^^^^^^