-
Notifications
You must be signed in to change notification settings - Fork 254
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
Macro error messages validating metadata and making suggestions #1339
Changes from 42 commits
e163e57
1760792
b297fc8
7a2678a
60c1e62
297d2c6
f47fd4e
03591a6
9e56546
50ed54e
dabc88c
cd73d31
fd718bf
985a46c
a6b580f
44c42c3
0724735
ee1e096
58b8fee
0c7d373
42643d8
8196b63
bbda16a
4259572
14775ab
69af6d1
f125b2b
602d459
b18d472
aaf9ded
05dbe3f
e54cb73
3a2a2a0
24cb3d7
e6ed42f
58071f5
a8d59a8
cac64ee
5ade376
ad1fdd7
50eeb0e
c676c4f
96c0a38
56c43cd
011e4b0
b884f64
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,11 +10,16 @@ use codec::Decode; | |
use darling::{ast::NestedMeta, FromMeta}; | ||
use proc_macro::TokenStream; | ||
use proc_macro_error::{abort_call_site, proc_macro_error}; | ||
use quote::ToTokens; | ||
use scale_typegen::typegen::{ | ||
settings::substitutes::path_segments, | ||
validation::{registry_contains_type_path, similar_type_paths_in_registry}, | ||
}; | ||
use subxt_codegen::{ | ||
fetch_metadata::{ | ||
fetch_metadata_from_file_blocking, fetch_metadata_from_url_blocking, MetadataVersion, Url, | ||
}, | ||
CodegenBuilder, CodegenError, | ||
CodegenBuilder, CodegenError, Metadata, | ||
}; | ||
use syn::{parse_macro_input, punctuated::Punctuated}; | ||
|
||
|
@@ -83,17 +88,21 @@ struct SubstituteType { | |
#[proc_macro_attribute] | ||
#[proc_macro_error] | ||
pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { | ||
let attr_args = match NestedMeta::parse_meta_list(args.into()) { | ||
Ok(v) => v, | ||
Err(e) => { | ||
return TokenStream::from(darling::Error::from(e).write_errors()); | ||
} | ||
}; | ||
let item_mod = parse_macro_input!(input as syn::ItemMod); | ||
let args = match RuntimeMetadataArgs::from_list(&attr_args) { | ||
Ok(v) => v, | ||
Err(e) => return TokenStream::from(e.write_errors()), | ||
}; | ||
match _subxt(args, parse_macro_input!(input as syn::ItemMod)) { | ||
Ok(e) => e, | ||
Err(e) => e, | ||
} | ||
} | ||
|
||
// Note: just an additional function to make early returns easier. | ||
fn _subxt(args: TokenStream, item_mod: syn::ItemMod) -> Result<TokenStream, TokenStream> { | ||
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: or |
||
let attr_args = NestedMeta::parse_meta_list(args.into()) | ||
.map_err(|e| TokenStream::from(darling::Error::from(e).write_errors()))?; | ||
let args = RuntimeMetadataArgs::from_list(&attr_args) | ||
.map_err(|e| TokenStream::from(e.write_errors()))?; | ||
|
||
// Fetch metadata first, because we need it to validate some of the chosen codegen options. | ||
let metadata = fetch_metadata(&args)?; | ||
|
||
let mut codegen = CodegenBuilder::new(); | ||
|
||
|
@@ -127,6 +136,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { | |
.collect(), | ||
); | ||
for d in args.derive_for_type { | ||
validate_type_path(&d.path.path, &metadata); | ||
codegen.add_derives_for_type(d.path, d.derive.into_iter(), d.recursive); | ||
} | ||
|
||
|
@@ -139,20 +149,65 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { | |
.collect(), | ||
); | ||
for d in args.attributes_for_type { | ||
validate_type_path(&d.path.path, &metadata); | ||
codegen.add_attributes_for_type(d.path, d.attributes.into_iter().map(|a| a.0), d.recursive) | ||
} | ||
|
||
// Insert type substitutions: | ||
for sub in args.substitute_type.into_iter() { | ||
validate_type_path(&sub.path, &metadata); | ||
codegen.set_type_substitute(sub.path, sub.with); | ||
} | ||
|
||
let code = codegen | ||
.generate(metadata) | ||
.map_err(|e| e.into_compile_error())?; | ||
|
||
Ok(code.into()) | ||
} | ||
|
||
/// Checks that a type is present in the type registry. If it is not found, abort with a | ||
/// helpful error message, showing the user alternative types, that have the same name, but are at different locations in the metadata. | ||
fn validate_type_path(path: &syn::Path, metadata: &Metadata) { | ||
let path_segments = path_segments(path); | ||
let ident = &path | ||
.segments | ||
.last() | ||
.expect("Empty path should be filtered out before already") | ||
.ident; | ||
if !registry_contains_type_path(metadata.types(), &path_segments) { | ||
let alternatives = similar_type_paths_in_registry(metadata.types(), path); | ||
let alternatives: String = if alternatives.is_empty() { | ||
format!("There is no Type with name `{ident}` in the provided metadata.") | ||
} else { | ||
let mut s = "A type with the same name is present at: ".to_owned(); | ||
for p in alternatives { | ||
s.push('\n'); | ||
s.push_str(&pretty_path(&p)); | ||
} | ||
s | ||
}; | ||
|
||
abort_call_site!( | ||
"Type `{}` does not exist at path `{}`\n\n{}", | ||
ident.to_string(), | ||
pretty_path(path), | ||
alternatives | ||
); | ||
} | ||
|
||
fn pretty_path(path: &syn::Path) -> String { | ||
path.to_token_stream().to_string().replace(' ', "") | ||
} | ||
} | ||
|
||
/// Fetches metadata in a blocking manner, either from a url (not recommended) or from a file path. | ||
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 don't get why fetching metadata from an URL is not recommended? :) 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. It is not recommended for users because the compilation blocks while fetching the metadata, which is a bit slow and (maybe flaky) compared to using metadata file. But yeah, maybe the docs of this function don't need to mention this. 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. Maybe we can say its not recommended for production use-cases. Do remember we added the ability to fetch the metadata from URL as a feature request from the |
||
fn fetch_metadata(args: &RuntimeMetadataArgs) -> Result<subxt_codegen::Metadata, TokenStream> { | ||
// Do we want to fetch unstable metadata? This only works if fetching from a URL. | ||
let unstable_metadata = args.unstable_metadata.is_present(); | ||
|
||
match ( | ||
args.runtime_metadata_path, | ||
args.runtime_metadata_insecure_url, | ||
let metadata = match ( | ||
&args.runtime_metadata_path, | ||
&args.runtime_metadata_insecure_url, | ||
) { | ||
(Some(rest_of_path), None) => { | ||
if unstable_metadata { | ||
|
@@ -164,16 +219,12 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { | |
let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); | ||
let root_path = std::path::Path::new(&root); | ||
let path = root_path.join(rest_of_path); | ||
let generated_code = fetch_metadata_from_file_blocking(&path) | ||
.map_err(CodegenError::from) | ||
fetch_metadata_from_file_blocking(&path) | ||
.and_then(|b| subxt_codegen::Metadata::decode(&mut &*b).map_err(Into::into)) | ||
.and_then(|m| codegen.generate(m).map_err(Into::into)) | ||
.unwrap_or_else(|e| e.into_compile_error()); | ||
|
||
generated_code.into() | ||
.map_err(|e| CodegenError::from(e).into_compile_error())? | ||
} | ||
(None, Some(url_string)) => { | ||
let url = Url::parse(&url_string).unwrap_or_else(|_| { | ||
let url = Url::parse(url_string).unwrap_or_else(|_| { | ||
abort_call_site!("Cannot download metadata; invalid url: {}", url_string) | ||
}); | ||
|
||
|
@@ -182,13 +233,10 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { | |
false => MetadataVersion::Latest, | ||
}; | ||
|
||
let generated_code = fetch_metadata_from_url_blocking(url, version) | ||
fetch_metadata_from_url_blocking(url, version) | ||
.map_err(CodegenError::from) | ||
.and_then(|b| subxt_codegen::Metadata::decode(&mut &*b).map_err(Into::into)) | ||
.and_then(|m| codegen.generate(m).map_err(Into::into)) | ||
.unwrap_or_else(|e| e.into_compile_error()); | ||
|
||
generated_code.into() | ||
.map_err(|e| e.into_compile_error())? | ||
} | ||
(None, None) => { | ||
abort_call_site!( | ||
|
@@ -200,5 +248,6 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { | |
"Only one of 'runtime_metadata_path' or 'runtime_metadata_insecure_url' can be provided" | ||
) | ||
} | ||
} | ||
}; | ||
Ok(metadata) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#[subxt::subxt( | ||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale", | ||
substitute_type( | ||
path = "sp_runtime::multiaddress::Event", | ||
with = "crate::MyEvent" | ||
) | ||
)] | ||
pub mod node_runtime {} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
error: Type `Event` does not exist at path `sp_runtime::multiaddress::Event` | ||
|
||
A type with the same name is present at: | ||
frame_system::pallet::Event | ||
pallet_balances::pallet::Event | ||
pallet_multisig::pallet::Event | ||
--> src/incorrect/substitute_at_wrong_path.rs:1:1 | ||
| | ||
1 | / #[subxt::subxt( | ||
2 | | runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale", | ||
3 | | substitute_type( | ||
4 | | path = "sp_runtime::multiaddress::Event", | ||
5 | | with = "crate::MyEvent" | ||
6 | | ) | ||
7 | | )] | ||
| |__^ | ||
| | ||
= note: this error originates in the attribute macro `subxt::subxt` (in Nightly builds, run with -Z macro-backtrace for more info) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
error: Type Generation failed: Type substitution error: `substitute_type(with = <path>)` must be a path prefixed with 'crate::' or '::' | ||
--> src/incorrect/substitute_path_not_absolute.rs:5:16 | ||
| | ||
5 | with = "sp_runtime::Perbill" | ||
| ^^^^^^^^^^^^^^^^^^^^^ | ||
5 | with = "my_mod::DispatchInfo" | ||
| ^^^^^^^^^^^^^^^^^^^^^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do wonder, did this happen as a result of some linting extensions from your IDE?
We could revert this to keep consistency with the dependencies of this file (
syn = { workspace = true }
)