Skip to content

Commit b649f4a

Browse files
lexnvbkchrgui1117shawntabrizi
authored
Metadata V16 (unstable): Enrich metadata with associated types of config traits (#5274)
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. ### API Design - There is nothing to collect from the associated types: ```rust #[pallet::config] pub trait Config: frame_system::Config { // Runtime events already propagated to the metadata. type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; // Constants are already propagated. #[pallet::constant] type MyGetParam2: Get<u32>; } ``` - Default automatic collection of associated types that require TypeInfo or Parameter bounds: ```rust #[pallet::config] pub trait Config: frame_system::Config { // Runtime events already propagated to the metadata. type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; // Constants are already propagated. #[pallet::constant] type MyGetParam2: Get<u32>; // Associated type included by default, because it requires TypeInfo bound. /// Nonce doc. type Nonce: TypeInfo; // Associated type included by default, because it requires // Parameter bound (indirect TypeInfo). type AccountData: Parameter; // Associated type without metadata bounds, not included. type NotIncluded: From<u8>; } ``` - Disable automatic collection ```rust // Associated types are not collected by default. #[pallet::config(without_metadata)] pub trait Config: frame_system::Config { // Runtime events already propagated to the metadata. type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; // Constants are already propagated. #[pallet::constant] type MyGetParam2: Get<u32>; // Explicitly include associated types. #[pallet::include_metadata] type Nonce: TypeInfo; type AccountData: Parameter; type NotIncluded: From<u8>; } ``` Builds on top of the PoC: #4358 Closes: #4519 cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile <[email protected]> Co-authored-by: Bastian Köcher <[email protected]> Co-authored-by: Guillaume Thiolliere <[email protected]> Co-authored-by: Shawn Tabrizi <[email protected]>
1 parent 2c41656 commit b649f4a

23 files changed

+917
-11
lines changed

prdoc/pr_5274.prdoc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
title: Enrich metadata IR with associated types of config traits
2+
3+
doc:
4+
- audience: Runtime Dev
5+
description: |
6+
This feature is part of the upcoming metadata V16. The associated types of the `Config` trait that require the `TypeInfo`
7+
or `Parameter` bounds are included in the metadata of the pallet. The metadata is not yet exposed to the end-user, however
8+
the metadata intermediate representation (IR) contains these types.
9+
10+
Developers can opt out of metadata collection of the associated types by specifying `without_metadata` optional attribute
11+
to the `#[pallet::config]`.
12+
13+
Furthermore, the `without_metadata` argument can be used in combination with the newly added `#[pallet::include_metadata]`
14+
attribute to selectively include only certain associated types in the metadata collection.
15+
16+
crates:
17+
- name: frame-support
18+
bump: patch
19+
- name: frame-support-procedural
20+
bump: patch
21+
- name: frame-support-procedural-tools
22+
bump: patch
23+
- name: sp-metadata-ir
24+
bump: major

substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub fn expand_runtime_metadata(
4949
let event = expand_pallet_metadata_events(&filtered_names, runtime, decl);
5050
let constants = expand_pallet_metadata_constants(runtime, decl);
5151
let errors = expand_pallet_metadata_errors(runtime, decl);
52+
let associated_types = expand_pallet_metadata_associated_types(runtime, decl);
5253
let docs = expand_pallet_metadata_docs(runtime, decl);
5354
let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
5455
let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
@@ -70,6 +71,7 @@ pub fn expand_runtime_metadata(
7071
constants: #constants,
7172
error: #errors,
7273
docs: #docs,
74+
associated_types: #associated_types,
7375
deprecation_info: #deprecation_info,
7476
}
7577
}
@@ -261,3 +263,12 @@ fn expand_pallet_metadata_docs(runtime: &Ident, decl: &Pallet) -> TokenStream {
261263
#path::Pallet::<#runtime #(, #path::#instance)*>::pallet_documentation_metadata()
262264
}
263265
}
266+
267+
fn expand_pallet_metadata_associated_types(runtime: &Ident, decl: &Pallet) -> TokenStream {
268+
let path = &decl.path;
269+
let instance = decl.instance.as_ref().into_iter();
270+
271+
quote! {
272+
#path::Pallet::<#runtime #(, #path::#instance)*>::pallet_associated_types_metadata()
273+
}
274+
}

substrate/frame/support/procedural/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,15 @@ pub fn event(_: TokenStream, _: TokenStream) -> TokenStream {
972972
pallet_macro_stub()
973973
}
974974

975+
///
976+
/// ---
977+
///
978+
/// Documentation for this macro can be found at `frame_support::pallet_macros::include_metadata`.
979+
#[proc_macro_attribute]
980+
pub fn include_metadata(_: TokenStream, _: TokenStream) -> TokenStream {
981+
pallet_macro_stub()
982+
}
983+
975984
///
976985
/// ---
977986
///

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,51 @@ Consequently, a runtime that wants to include this pallet must implement this tr
9595
_ => Default::default(),
9696
}
9797
}
98+
99+
/// Generate the metadata for the associated types of the config trait.
100+
///
101+
/// Implements the `pallet_associated_types_metadata` function for the pallet.
102+
pub fn expand_config_metadata(def: &Def) -> proc_macro2::TokenStream {
103+
let frame_support = &def.frame_support;
104+
let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site());
105+
let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site());
106+
let pallet_ident = &def.pallet_struct.pallet;
107+
let trait_use_gen = &def.trait_use_generics(proc_macro2::Span::call_site());
108+
109+
let mut where_clauses = vec![&def.config.where_clause];
110+
where_clauses.extend(def.extra_constants.iter().map(|d| &d.where_clause));
111+
let completed_where_clause = super::merge_where_clauses(&where_clauses);
112+
113+
let types = def.config.associated_types_metadata.iter().map(|metadata| {
114+
let ident = &metadata.ident;
115+
let span = ident.span();
116+
let ident_str = ident.to_string();
117+
let cfgs = &metadata.cfg;
118+
119+
let no_docs = vec![];
120+
let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &metadata.doc };
121+
122+
quote::quote_spanned!(span => {
123+
#( #cfgs ) *
124+
#frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR {
125+
name: #ident_str,
126+
ty: #frame_support::__private::scale_info::meta_type::<
127+
<T as Config #trait_use_gen>::#ident
128+
>(),
129+
docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ],
130+
}
131+
})
132+
});
133+
134+
quote::quote!(
135+
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause {
136+
137+
#[doc(hidden)]
138+
pub fn pallet_associated_types_metadata()
139+
-> #frame_support::__private::sp_std::vec::Vec<#frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR>
140+
{
141+
#frame_support::__private::sp_std::vec![ #( #types ),* ]
142+
}
143+
}
144+
)
145+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream {
6060
let constants = constants::expand_constants(&mut def);
6161
let pallet_struct = pallet_struct::expand_pallet_struct(&mut def);
6262
let config = config::expand_config(&mut def);
63+
let associated_types = config::expand_config_metadata(&def);
6364
let call = call::expand_call(&mut def);
6465
let tasks = tasks::expand_tasks(&mut def);
6566
let error = error::expand_error(&mut def);
@@ -101,6 +102,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*]
101102
#constants
102103
#pallet_struct
103104
#config
105+
#associated_types
104106
#call
105107
#tasks
106108
#error

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

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
// limitations under the License.
1717

1818
use super::helper;
19-
use frame_support_procedural_tools::{get_doc_literals, is_using_frame_crate};
19+
use frame_support_procedural_tools::{get_cfg_attributes, get_doc_literals, is_using_frame_crate};
2020
use quote::ToTokens;
21-
use syn::{spanned::Spanned, token, Token};
21+
use syn::{spanned::Spanned, token, Token, TraitItemType};
2222

2323
/// List of additional token to be used for parsing.
2424
mod keyword {
@@ -36,6 +36,7 @@ mod keyword {
3636
syn::custom_keyword!(no_default);
3737
syn::custom_keyword!(no_default_bounds);
3838
syn::custom_keyword!(constant);
39+
syn::custom_keyword!(include_metadata);
3940
}
4041

4142
#[derive(Default)]
@@ -55,6 +56,8 @@ pub struct ConfigDef {
5556
pub has_instance: bool,
5657
/// Const associated type.
5758
pub consts_metadata: Vec<ConstMetadataDef>,
59+
/// Associated types metadata.
60+
pub associated_types_metadata: Vec<AssociatedTypeMetadataDef>,
5861
/// Whether the trait has the associated type `Event`, note that those bounds are
5962
/// checked:
6063
/// * `IsType<Self as frame_system::Config>::RuntimeEvent`
@@ -70,6 +73,26 @@ pub struct ConfigDef {
7073
pub default_sub_trait: Option<DefaultTrait>,
7174
}
7275

76+
/// Input definition for an associated type in pallet config.
77+
pub struct AssociatedTypeMetadataDef {
78+
/// Name of the associated type.
79+
pub ident: syn::Ident,
80+
/// The doc associated.
81+
pub doc: Vec<syn::Expr>,
82+
/// The cfg associated.
83+
pub cfg: Vec<syn::Attribute>,
84+
}
85+
86+
impl From<&syn::TraitItemType> for AssociatedTypeMetadataDef {
87+
fn from(trait_ty: &syn::TraitItemType) -> Self {
88+
let ident = trait_ty.ident.clone();
89+
let doc = get_doc_literals(&trait_ty.attrs);
90+
let cfg = get_cfg_attributes(&trait_ty.attrs);
91+
92+
Self { ident, doc, cfg }
93+
}
94+
}
95+
7396
/// Input definition for a constant in pallet config.
7497
pub struct ConstMetadataDef {
7598
/// Name of the associated type.
@@ -146,6 +169,8 @@ pub enum PalletAttrType {
146169
NoBounds(keyword::no_default_bounds),
147170
#[peek(keyword::constant, name = "constant")]
148171
Constant(keyword::constant),
172+
#[peek(keyword::include_metadata, name = "include_metadata")]
173+
IncludeMetadata(keyword::include_metadata),
149174
}
150175

151176
/// Parsing for `#[pallet::X]`
@@ -322,12 +347,32 @@ pub fn replace_self_by_t(input: proc_macro2::TokenStream) -> proc_macro2::TokenS
322347
.collect()
323348
}
324349

350+
/// Check that the trait item requires the `TypeInfo` bound (or similar).
351+
fn contains_type_info_bound(ty: &TraitItemType) -> bool {
352+
const KNOWN_TYPE_INFO_BOUNDS: &[&str] = &[
353+
// Explicit TypeInfo trait.
354+
"TypeInfo",
355+
// Implicit known substrate traits that implement type info.
356+
// Note: Aim to keep this list as small as possible.
357+
"Parameter",
358+
];
359+
360+
ty.bounds.iter().any(|bound| {
361+
let syn::TypeParamBound::Trait(bound) = bound else { return false };
362+
363+
KNOWN_TYPE_INFO_BOUNDS
364+
.iter()
365+
.any(|known| bound.path.segments.last().map_or(false, |last| last.ident == *known))
366+
})
367+
}
368+
325369
impl ConfigDef {
326370
pub fn try_from(
327371
frame_system: &syn::Path,
328372
index: usize,
329373
item: &mut syn::Item,
330374
enable_default: bool,
375+
disable_associated_metadata: bool,
331376
) -> syn::Result<Self> {
332377
let syn::Item::Trait(item) = item else {
333378
let msg = "Invalid pallet::config, expected trait definition";
@@ -368,6 +413,7 @@ impl ConfigDef {
368413

369414
let mut has_event_type = false;
370415
let mut consts_metadata = vec![];
416+
let mut associated_types_metadata = vec![];
371417
let mut default_sub_trait = if enable_default {
372418
Some(DefaultTrait {
373419
items: Default::default(),
@@ -383,6 +429,7 @@ impl ConfigDef {
383429
let mut already_no_default = false;
384430
let mut already_constant = false;
385431
let mut already_no_default_bounds = false;
432+
let mut already_collected_associated_type = None;
386433

387434
while let Ok(Some(pallet_attr)) =
388435
helper::take_first_item_pallet_attr::<PalletAttr>(trait_item)
@@ -403,11 +450,29 @@ impl ConfigDef {
403450
trait_item.span(),
404451
"Invalid #[pallet::constant] in #[pallet::config], expected type item",
405452
)),
453+
// Pallet developer has explicitly requested to include metadata for this associated type.
454+
//
455+
// They must provide a type item that implements `TypeInfo`.
456+
(PalletAttrType::IncludeMetadata(_), syn::TraitItem::Type(ref typ)) => {
457+
if already_collected_associated_type.is_some() {
458+
return Err(syn::Error::new(
459+
pallet_attr._bracket.span.join(),
460+
"Duplicate #[pallet::include_metadata] attribute not allowed.",
461+
));
462+
}
463+
already_collected_associated_type = Some(pallet_attr._bracket.span.join());
464+
associated_types_metadata.push(AssociatedTypeMetadataDef::from(AssociatedTypeMetadataDef::from(typ)));
465+
}
466+
(PalletAttrType::IncludeMetadata(_), _) =>
467+
return Err(syn::Error::new(
468+
pallet_attr._bracket.span.join(),
469+
"Invalid #[pallet::include_metadata] in #[pallet::config], expected type item",
470+
)),
406471
(PalletAttrType::NoDefault(_), _) => {
407472
if !enable_default {
408473
return Err(syn::Error::new(
409474
pallet_attr._bracket.span.join(),
410-
"`#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` \
475+
"`#[pallet::no_default]` can only be used if `#[pallet::config(with_default)]` \
411476
has been specified"
412477
));
413478
}
@@ -439,6 +504,47 @@ impl ConfigDef {
439504
}
440505
}
441506

507+
if let Some(span) = already_collected_associated_type {
508+
// Events and constants are already propagated to the metadata
509+
if is_event {
510+
return Err(syn::Error::new(
511+
span,
512+
"Invalid #[pallet::include_metadata] for `type RuntimeEvent`. \
513+
The associated type `RuntimeEvent` is already collected in the metadata.",
514+
))
515+
}
516+
517+
if already_constant {
518+
return Err(syn::Error::new(
519+
span,
520+
"Invalid #[pallet::include_metadata]: conflict with #[pallet::constant]. \
521+
Pallet constant already collect the metadata for the type.",
522+
))
523+
}
524+
525+
if let syn::TraitItem::Type(ref ty) = trait_item {
526+
if !contains_type_info_bound(ty) {
527+
let msg = format!(
528+
"Invalid #[pallet::include_metadata] in #[pallet::config], collected type `{}` \
529+
does not implement `TypeInfo` or `Parameter`",
530+
ty.ident,
531+
);
532+
return Err(syn::Error::new(span, msg));
533+
}
534+
}
535+
} else {
536+
// Metadata of associated types is collected by default, if the associated type
537+
// implements `TypeInfo`, or a similar trait that requires the `TypeInfo` bound.
538+
if !disable_associated_metadata && !is_event && !already_constant {
539+
if let syn::TraitItem::Type(ref ty) = trait_item {
540+
// Collect the metadata of the associated type if it implements `TypeInfo`.
541+
if contains_type_info_bound(ty) {
542+
associated_types_metadata.push(AssociatedTypeMetadataDef::from(ty));
543+
}
544+
}
545+
}
546+
}
547+
442548
if !already_no_default && enable_default {
443549
default_sub_trait
444550
.as_mut()
@@ -481,6 +587,7 @@ impl ConfigDef {
481587
index,
482588
has_instance,
483589
consts_metadata,
590+
associated_types_metadata,
484591
has_event_type,
485592
where_clause,
486593
default_sub_trait,

0 commit comments

Comments
 (0)