Skip to content
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

Metadata V16 (unstable): Enrich metadata with associated types of config traits #5274

Merged
merged 54 commits into from
Oct 16, 2024

Conversation

lexnv
Copy link
Contributor

@lexnv lexnv commented Aug 7, 2024

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:
#[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:
	#[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
// 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

@lexnv lexnv added I5-enhancement An additional feature request. T1-FRAME This PR/Issue is related to core FRAME, the framework. T11-documentation This PR/Issue is related to documentation. D1-medium Can be fixed by a coder with good Rust knowledge but little knowledge of the codebase. labels Aug 8, 2024
@lexnv lexnv marked this pull request as ready for review August 8, 2024 10:36
@lexnv lexnv requested a review from a team as a code owner August 8, 2024 10:36
@paritytech-review-bot paritytech-review-bot bot requested a review from a team October 10, 2024 05:36
Comment on lines 1 to 6
error[E0277]: the trait bound `<T as pallet::Config>::MyNonScaleTypeInfo: TypeInfo` is not satisfied
--> tests/pallet_ui/config_metadata_non_type_info.rs:29:8
|
29 | type MyNonScaleTypeInfo;
| ^^^^^^^^^^^^^^^^^^ the trait `TypeInfo` is not implemented for `<T as pallet::Config>::MyNonScaleTypeInfo`
|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I prefer that error originating from expansion from an attribute point directly to the attribute.

Like here it would point to #[pallet::include_metadata], instead of the associated type ident.

This allows user to directly look at the doc of include_metadata and not be confused trying to understand it as a regular rust error.

But in this case I don't know, it is also fine as it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean now! Indeed, have added an extra check while collecting the metadata, we should have now a bit clearer error message here:

error: Invalid #[pallet::include_metadata] in #[pallet::config], collected type `MyNonScaleTypeInfo` does not implement `scale::TypeInfo`
  --> tests/pallet_ui/config_metadata_non_type_info.rs:28:4

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually what I imagine was something like:

let span = /* span of `#[pallet::include_metadata]"  or otherwise call site for automatic inclusion*/
quote_spanned(span => /* expansion related to include metadata or automatic inclusion*/)

But everything is good to me, I don't have much opinion.
Your current implementation is more restrictive but more explicit so it can be the best.

@gui1117
Copy link
Contributor

gui1117 commented Oct 10, 2024

Looks good to me, but zombienet tests are failing somehow.

@lexnv
Copy link
Contributor Author

lexnv commented Oct 11, 2024

@bkchr could you please have a quick look whenever you have some time? 🙏

Thanks everyone for the reviews here 🙏

if !contains_type_info_bound(ty) {
let msg = format!(
"Invalid #[pallet::include_metadata] in #[pallet::config], collected type `{}` \
does not implement `scale::TypeInfo`",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick, maybe we can say: does not explicitly bound TypeInfo or Parameter.

ty.bounds.iter().any(|bound| {
let syn::TypeParamBound::Trait(bound) = bound else { return false };

KNOWN_TYPE_INFO_BOUNDS.iter().any(|known| bound.path.is_ident(known))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this will not match scale::TypeInfo but only TypeInfo, isn't it?

What I mean is that we do the match on the whole path, not the last segment, no?

If we want to support only this. Then we can say in the error message of include_metadata something like: "Note: only ident is supported, e.g. scale::TypeInfo is not detected, only TypeInfo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just take the last element of the path to improve the detection?

Copy link
Member

@bkchr bkchr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly left some nitpicks. Generally looking good. Good work 👍

}

if let syn::TraitItem::Type(ref ty) = trait_item {
if !contains_type_info_bound(ty) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh but maybe not possible. Not sure, but worth a try.

//
// They must provide a type item that implements `TypeInfo`.
(PalletAttrType::IncludeMetadata(_), syn::TraitItem::Type(ref typ)) => {
already_collected_associated_type = Some(pallet_attr._bracket.span.join());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be an error if the attribute is passed multiple times.

ty.bounds.iter().any(|bound| {
let syn::TypeParamBound::Trait(bound) = bound else { return false };

KNOWN_TYPE_INFO_BOUNDS.iter().any(|known| bound.path.is_ident(known))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just take the last element of the path to improve the detection?

Comment on lines 699 to 723
let fields: syn::punctuated::Punctuated<ConfigValue, syn::Token![,]> =
inside_config.parse_terminated(ConfigValue::parse, syn::Token![,])?;
let config_values = fields.iter().collect::<Vec<_>>();

let frequencies = config_values.iter().fold(HashMap::new(), |mut map, val| {
let string_name = match val {
ConfigValue::WithDefault(_) => "with_default",
ConfigValue::WithoutAutomaticMetadata(_) => "without_automatic_metadata",
}
.to_string();
map.entry(string_name).and_modify(|frq| *frq += 1).or_insert(1);
map
});
let with_default = frequencies.get("with_default").copied().unwrap_or(0) > 0;
let without_automatic_metadata =
frequencies.get("without_automatic_metadata").copied().unwrap_or(0) > 0;

let duplicates = frequencies
.into_iter()
.filter_map(|(name, frq)| if frq > 1 { Some(name) } else { None })
.collect::<Vec<_>>();
if !duplicates.is_empty() {
let msg = format!("Invalid duplicated attribute for `#[pallet::config]`. Please remove duplicates: {}.", duplicates.join(", "));
return Err(syn::Error::new(span, msg));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just having some options would be easier here? But yeah, the code works the way it is written.

@lexnv lexnv enabled auto-merge October 15, 2024 16:09
@lexnv lexnv added this pull request to the merge queue Oct 16, 2024
Merged via the queue into master with commit b649f4a Oct 16, 2024
189 of 193 checks passed
@lexnv lexnv deleted the lexnv/metadata-v16-associated-types branch October 16, 2024 10:30
github-merge-queue bot pushed a commit that referenced this pull request Nov 27, 2024
This PR exposes the *unstable* metadata V16. The metadata is exposed
under the unstable u32::MAX number.

Developers can start experimenting with the new features of the metadata
v16. *Please note that this metadata is under development and expect
breaking changes until stabilization.*

The `ExtrinsicMetadata` trait receives a breaking change. Its associated
type `VERSION` is rename to `VERSIONS` and now supports a constant
static list of metadata versions.
The versions implemented for `UncheckedExtrinsic` are v4 (legacy
version) and v5 (new version).

For metadata collection, it is assumed that all `TransactionExtensions`
are under version 0.

Builds on top of: #5274

Closes: #5980
Closes: #5347
Closes: #5285

cc @paritytech/subxt-team

---------

Signed-off-by: Alexandru Vasile <[email protected]>
Co-authored-by: Niklas Adolfsson <[email protected]>
Co-authored-by: Bastian Köcher <[email protected]>
Co-authored-by: James Wilson <[email protected]>
Co-authored-by: GitHub Action <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
D1-medium Can be fixed by a coder with good Rust knowledge but little knowledge of the codebase. I5-enhancement An additional feature request. T1-FRAME This PR/Issue is related to core FRAME, the framework. T11-documentation This PR/Issue is related to documentation.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Metadata V16: Enrich metadata with associated types of config traits
7 participants