-
Notifications
You must be signed in to change notification settings - Fork 959
Metadata V16 (unstable): Enrich metadata with associated types of config traits #5274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7e8baee
1c12f15
20b22d8
350efce
3e97971
5d8021e
5679244
958611e
7f26b67
cc001f3
b2803e2
94007d8
75d2697
ebcb4a0
5115021
aba029a
6502c83
e9571cb
31bf284
7429eb7
8a0c138
c6c1800
90c100c
2982c59
f703e4d
fbd36bc
6be9ef3
c9b0e2a
0382d32
e52ba1d
45f86eb
fb8b2d4
4846726
d4e597d
a453595
8b9549d
cf6749b
54e6d2a
610bdc2
b18a2f9
678944a
d43743c
0928d78
8fd0180
ac08308
1296dc2
c1f204f
bc0f528
b125a1e
39a9d4b
ac92bc6
e96b36d
e91da23
f3e3e1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
title: Enrich metadata IR with associated types of config traits | ||
|
||
doc: | ||
- audience: Runtime Dev | ||
description: | | ||
This feature is part of the upcoming metadata V16. The associated types of the `Config` trait that require the `TypeInfo` | ||
or `Parameter` bounds are included in the metadata of the pallet. The metadata is not yet exposed to the end-user, however | ||
the metadata intermediate representation (IR) contains these types. | ||
|
||
Developers can opt out of metadata collection of the associated types by specifying `without_metadata` optional attribute | ||
to the `#[pallet::config]`. | ||
|
||
Furthermore, the `without_metadata` argument can be used in combination with the newly added `#[pallet::include_metadata]` | ||
attribute to selectively include only certain associated types in the metadata collection. | ||
|
||
crates: | ||
- name: frame-support | ||
bump: patch | ||
- name: frame-support-procedural | ||
bump: patch | ||
- name: frame-support-procedural-tools | ||
bump: patch | ||
- name: sp-metadata-ir | ||
bump: major |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,9 +16,9 @@ | |
// limitations under the License. | ||
|
||
use super::helper; | ||
use frame_support_procedural_tools::{get_doc_literals, is_using_frame_crate}; | ||
use frame_support_procedural_tools::{get_cfg_attributes, get_doc_literals, is_using_frame_crate}; | ||
use quote::ToTokens; | ||
use syn::{spanned::Spanned, token, Token}; | ||
use syn::{spanned::Spanned, token, Token, TraitItemType}; | ||
|
||
/// List of additional token to be used for parsing. | ||
mod keyword { | ||
|
@@ -36,6 +36,7 @@ mod keyword { | |
syn::custom_keyword!(no_default); | ||
syn::custom_keyword!(no_default_bounds); | ||
syn::custom_keyword!(constant); | ||
syn::custom_keyword!(include_metadata); | ||
} | ||
|
||
#[derive(Default)] | ||
|
@@ -55,6 +56,8 @@ pub struct ConfigDef { | |
pub has_instance: bool, | ||
/// Const associated type. | ||
pub consts_metadata: Vec<ConstMetadataDef>, | ||
/// Associated types metadata. | ||
pub associated_types_metadata: Vec<AssociatedTypeMetadataDef>, | ||
/// Whether the trait has the associated type `Event`, note that those bounds are | ||
/// checked: | ||
/// * `IsType<Self as frame_system::Config>::RuntimeEvent` | ||
|
@@ -70,6 +73,26 @@ pub struct ConfigDef { | |
pub default_sub_trait: Option<DefaultTrait>, | ||
} | ||
|
||
/// Input definition for an associated type in pallet config. | ||
pub struct AssociatedTypeMetadataDef { | ||
/// Name of the associated type. | ||
pub ident: syn::Ident, | ||
/// The doc associated. | ||
pub doc: Vec<syn::Expr>, | ||
/// The cfg associated. | ||
pub cfg: Vec<syn::Attribute>, | ||
} | ||
|
||
impl From<&syn::TraitItemType> for AssociatedTypeMetadataDef { | ||
fn from(trait_ty: &syn::TraitItemType) -> Self { | ||
let ident = trait_ty.ident.clone(); | ||
let doc = get_doc_literals(&trait_ty.attrs); | ||
let cfg = get_cfg_attributes(&trait_ty.attrs); | ||
|
||
Self { ident, doc, cfg } | ||
} | ||
} | ||
|
||
/// Input definition for a constant in pallet config. | ||
pub struct ConstMetadataDef { | ||
/// Name of the associated type. | ||
|
@@ -146,6 +169,8 @@ pub enum PalletAttrType { | |
NoBounds(keyword::no_default_bounds), | ||
#[peek(keyword::constant, name = "constant")] | ||
Constant(keyword::constant), | ||
#[peek(keyword::include_metadata, name = "include_metadata")] | ||
IncludeMetadata(keyword::include_metadata), | ||
} | ||
|
||
/// Parsing for `#[pallet::X]` | ||
|
@@ -322,12 +347,32 @@ pub fn replace_self_by_t(input: proc_macro2::TokenStream) -> proc_macro2::TokenS | |
.collect() | ||
} | ||
|
||
/// Check that the trait item requires the `TypeInfo` bound (or similar). | ||
fn contains_type_info_bound(ty: &TraitItemType) -> bool { | ||
const KNOWN_TYPE_INFO_BOUNDS: &[&str] = &[ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Why not a Set? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we'd need some feature similar to rust-lang/rust#93140 to make the hashSet const here |
||
// Explicit TypeInfo trait. | ||
"TypeInfo", | ||
// Implicit known substrate traits that implement type info. | ||
// Note: Aim to keep this list as small as possible. | ||
"Parameter", | ||
]; | ||
|
||
ty.bounds.iter().any(|bound| { | ||
let syn::TypeParamBound::Trait(bound) = bound else { return false }; | ||
|
||
KNOWN_TYPE_INFO_BOUNDS | ||
.iter() | ||
.any(|known| bound.path.segments.last().map_or(false, |last| last.ident == *known)) | ||
}) | ||
} | ||
|
||
impl ConfigDef { | ||
pub fn try_from( | ||
frame_system: &syn::Path, | ||
index: usize, | ||
item: &mut syn::Item, | ||
enable_default: bool, | ||
disable_associated_metadata: bool, | ||
) -> syn::Result<Self> { | ||
let syn::Item::Trait(item) = item else { | ||
let msg = "Invalid pallet::config, expected trait definition"; | ||
|
@@ -368,6 +413,7 @@ impl ConfigDef { | |
|
||
let mut has_event_type = false; | ||
let mut consts_metadata = vec![]; | ||
let mut associated_types_metadata = vec![]; | ||
let mut default_sub_trait = if enable_default { | ||
Some(DefaultTrait { | ||
items: Default::default(), | ||
|
@@ -383,6 +429,7 @@ impl ConfigDef { | |
let mut already_no_default = false; | ||
let mut already_constant = false; | ||
let mut already_no_default_bounds = false; | ||
let mut already_collected_associated_type = None; | ||
|
||
while let Ok(Some(pallet_attr)) = | ||
helper::take_first_item_pallet_attr::<PalletAttr>(trait_item) | ||
|
@@ -403,11 +450,29 @@ impl ConfigDef { | |
trait_item.span(), | ||
"Invalid #[pallet::constant] in #[pallet::config], expected type item", | ||
)), | ||
// Pallet developer has explicitly requested to include metadata for this associated type. | ||
// | ||
// They must provide a type item that implements `TypeInfo`. | ||
(PalletAttrType::IncludeMetadata(_), syn::TraitItem::Type(ref typ)) => { | ||
if already_collected_associated_type.is_some() { | ||
return Err(syn::Error::new( | ||
pallet_attr._bracket.span.join(), | ||
"Duplicate #[pallet::include_metadata] attribute not allowed.", | ||
)); | ||
} | ||
already_collected_associated_type = Some(pallet_attr._bracket.span.join()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be an error if the attribute is passed multiple times. |
||
associated_types_metadata.push(AssociatedTypeMetadataDef::from(AssociatedTypeMetadataDef::from(typ))); | ||
} | ||
(PalletAttrType::IncludeMetadata(_), _) => | ||
return Err(syn::Error::new( | ||
pallet_attr._bracket.span.join(), | ||
"Invalid #[pallet::include_metadata] in #[pallet::config], expected type item", | ||
)), | ||
(PalletAttrType::NoDefault(_), _) => { | ||
if !enable_default { | ||
return Err(syn::Error::new( | ||
pallet_attr._bracket.span.join(), | ||
"`#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` \ | ||
"`#[pallet::no_default]` can only be used if `#[pallet::config(with_default)]` \ | ||
has been specified" | ||
)); | ||
} | ||
|
@@ -439,6 +504,47 @@ impl ConfigDef { | |
} | ||
} | ||
|
||
if let Some(span) = already_collected_associated_type { | ||
// Events and constants are already propagated to the metadata | ||
if is_event { | ||
return Err(syn::Error::new( | ||
span, | ||
"Invalid #[pallet::include_metadata] for `type RuntimeEvent`. \ | ||
The associated type `RuntimeEvent` is already collected in the metadata.", | ||
)) | ||
} | ||
|
||
if already_constant { | ||
return Err(syn::Error::new( | ||
span, | ||
"Invalid #[pallet::include_metadata]: conflict with #[pallet::constant]. \ | ||
Pallet constant already collect the metadata for the type.", | ||
)) | ||
} | ||
|
||
if let syn::TraitItem::Type(ref ty) = trait_item { | ||
if !contains_type_info_bound(ty) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we implement here a mix of: https://docs.rs/static_assertions/1.1.0/src/static_assertions/assert_impl.rs.html#329-356 and This should give some very nice error message. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh but maybe not possible. Not sure, but worth a try. |
||
let msg = format!( | ||
"Invalid #[pallet::include_metadata] in #[pallet::config], collected type `{}` \ | ||
does not implement `TypeInfo` or `Parameter`", | ||
ty.ident, | ||
); | ||
return Err(syn::Error::new(span, msg)); | ||
} | ||
} | ||
} else { | ||
// Metadata of associated types is collected by default, if the associated type | ||
// implements `TypeInfo`, or a similar trait that requires the `TypeInfo` bound. | ||
if !disable_associated_metadata && !is_event && !already_constant { | ||
if let syn::TraitItem::Type(ref ty) = trait_item { | ||
// Collect the metadata of the associated type if it implements `TypeInfo`. | ||
if contains_type_info_bound(ty) { | ||
associated_types_metadata.push(AssociatedTypeMetadataDef::from(ty)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
if !already_no_default && enable_default { | ||
default_sub_trait | ||
.as_mut() | ||
|
@@ -481,6 +587,7 @@ impl ConfigDef { | |
index, | ||
has_instance, | ||
consts_metadata, | ||
associated_types_metadata, | ||
has_event_type, | ||
where_clause, | ||
default_sub_trait, | ||
|
Uh oh!
There was an error while loading. Please reload this page.