diff --git a/Cargo.lock b/Cargo.lock index 46c87115a0..b6949282e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ - "gimli 0.27.1", + "gimli 0.27.2", ] [[package]] @@ -294,9 +294,9 @@ dependencies = [ [[package]] name = "bounded-collections" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2aff4807e40f478132150d80b031f2461d88f061851afcab537d7600c24120" +checksum = "a071c348a5ef6da1d3a87166b408170b46002382b1dda83992b5c2208cefb370" dependencies = [ "log", "parity-scale-codec", @@ -405,13 +405,13 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.6" +version = "4.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" +checksum = "2f3061d6db6d8fcbbd4b05e057f2acace52e64e96b498c08c2d7a4e65addd340" dependencies = [ "bitflags", "clap_derive", - "clap_lex 0.3.1", + "clap_lex 0.3.2", "is-terminal", "once_cell", "strsim", @@ -420,9 +420,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.1.0" +version = "4.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +checksum = "34d122164198950ba84a918270a3bb3f7ededd25e15f7451673d986f55bd2667" dependencies = [ "heck", "proc-macro-error", @@ -442,9 +442,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" dependencies = [ "os_str_bytes", ] @@ -700,9 +700,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8" +checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" dependencies = [ "cc", "cxxbridge-flags", @@ -712,9 +712,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38" +checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" dependencies = [ "cc", "codespan-reporting", @@ -727,15 +727,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03" +checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" [[package]] name = "cxxbridge-macro" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263" +checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" dependencies = [ "proc-macro2", "quote", @@ -863,9 +863,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "ed25519" @@ -965,9 +965,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -1187,9 +1187,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "glob" @@ -1250,9 +1250,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" dependencies = [ "bytes", "fnv", @@ -1384,9 +1384,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -1597,9 +1597,9 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" +checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", @@ -1924,14 +1924,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -1940,15 +1940,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" -[[package]] -name = "nom8" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" -dependencies = [ - "memchr", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2044,9 +2035,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "oorandom" @@ -2284,9 +2275,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", "toml_edit", @@ -2887,9 +2878,9 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -3316,9 +3307,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "ss58-registry" -version = "1.38.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40c020d72bc0a9c5660bb71e4a6fdef081493583062c474740a7d59f55f0e7b" +checksum = "ecf0bd63593ef78eca595a7fc25e9a443ca46fe69fd472f8f09f5245cdcd769d" dependencies = [ "Inflector", "num-format", @@ -3427,7 +3418,7 @@ dependencies = [ name = "subxt-cli" version = "0.27.1" dependencies = [ - "clap 4.1.6", + "clap 4.1.7", "color-eyre", "frame-metadata", "hex", @@ -3453,12 +3444,12 @@ dependencies = [ "jsonrpsee", "parity-scale-codec", "pretty_assertions", - "proc-macro-error", "proc-macro2", "quote", "scale-info", "subxt-metadata", "syn", + "thiserror", "tokio", ] @@ -3692,19 +3683,19 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" [[package]] name = "toml_edit" -version = "0.18.1" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" dependencies = [ "indexmap", - "nom8", "toml_datetime", + "winnow", ] [[package]] @@ -4414,6 +4405,15 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "winnow" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/artifacts/polkadot_metadata_tmp.scale b/artifacts/polkadot_metadata_tmp.scale new file mode 100644 index 0000000000..95ef3bb90c Binary files /dev/null and b/artifacts/polkadot_metadata_tmp.scale differ diff --git a/cli/src/commands/codegen.rs b/cli/src/commands/codegen.rs index 8e84cf0d0b..132e7bf495 100644 --- a/cli/src/commands/codegen.rs +++ b/cli/src/commands/codegen.rs @@ -121,6 +121,14 @@ fn codegen( crate_path, should_gen_docs, ); - println!("{runtime_api}"); + match runtime_api { + Ok(runtime_api) => println!("{runtime_api}"), + Err(e) => { + // Print the error directly to avoid implementing `Send + Sync` on `CodegenError`. + use color_eyre::owo_colors::OwoColorize; + println!("{}", e.to_string().red()) + } + }; + Ok(()) } diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 03448c3f0c..0db3d068b8 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -18,7 +18,6 @@ darling = "0.14.3" frame-metadata = "15.0.0" heck = "0.4.1" proc-macro2 = "1.0.51" -proc-macro-error = "1.0.4" quote = "1.0.8" syn = "1.0.109" scale-info = "2.0.0" @@ -26,6 +25,7 @@ subxt-metadata = { version = "0.27.1", path = "../metadata" } jsonrpsee = { version = "0.16.0", features = ["async-client", "client-ws-transport", "http-client"] } hex = "0.4.3" tokio = { version = "1.25", features = ["macros", "rt-multi-thread"] } +thiserror = "1.0.24" [dev-dependencies] bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } diff --git a/codegen/src/api/calls.rs b/codegen/src/api/calls.rs index 1cf5306034..2f05a30815 100644 --- a/codegen/src/api/calls.rs +++ b/codegen/src/api/calls.rs @@ -2,6 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use super::CodegenError; use crate::{ types::{ CompositeDefFields, @@ -18,7 +19,6 @@ use heck::{ ToUpperCamelCase as _, }; use proc_macro2::TokenStream as TokenStream2; -use proc_macro_error::abort_call_site; use quote::{ format_ident, quote, @@ -41,12 +41,10 @@ pub fn generate_calls( types_mod_ident: &syn::Ident, crate_path: &CratePath, should_gen_docs: bool, -) -> TokenStream2 { +) -> Result { // Early return if the pallet has no calls. - let call = if let Some(ref calls) = pallet.calls { - calls - } else { - return quote!() + let Some(call) = &pallet.calls else { + return Ok(quote!()); }; let mut struct_defs = super::generate_structs_from_variants( @@ -56,7 +54,7 @@ pub fn generate_calls( "Call", crate_path, should_gen_docs, - ); + )?; let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs .iter_mut() .map(|(variant_name, struct_def)| { @@ -77,26 +75,20 @@ pub fn generate_calls( } CompositeDefFields::NoFields => Default::default(), CompositeDefFields::Unnamed(_) => { - abort_call_site!( - "Call variant for type {} must have all named fields", - call.ty.id() - ) + return Err(CodegenError::InvalidCallVariant(call.ty.id())) } }; let pallet_name = &pallet.name; let call_name = &variant_name; let struct_name = &struct_def.name; - let call_hash = - subxt_metadata::get_call_hash(metadata, pallet_name, call_name) - .unwrap_or_else(|_| { - abort_call_site!( - "Metadata information for the call {}_{} could not be found", - pallet_name, - call_name - ) - }); - + let Ok(call_hash) = + subxt_metadata::get_call_hash(metadata, pallet_name, call_name) else { + return Err(CodegenError::MissingCallMetadata( + pallet_name.into(), + call_name.to_string(), + )) + }; let fn_name = format_ident!("{}", variant_name.to_snake_case()); // Propagate the documentation just to `TransactionApi` methods, while // draining the documentation of inner call structures. @@ -121,8 +113,11 @@ pub fn generate_calls( ) } }; - (call_struct, client_fn) + + Ok((call_struct, client_fn)) }) + .collect::, _>>()? + .into_iter() .unzip(); let call_ty = type_gen.resolve_type(call.ty.id()); @@ -131,7 +126,7 @@ pub fn generate_calls( .then_some(quote! { #( #[doc = #docs ] )* }) .unwrap_or_default(); - quote! { + Ok(quote! { #docs pub mod calls { use super::root_mod; @@ -147,5 +142,5 @@ pub fn generate_calls( #( #call_fns )* } } - } + }) } diff --git a/codegen/src/api/constants.rs b/codegen/src/api/constants.rs index f6666ba103..2fdbe85225 100644 --- a/codegen/src/api/constants.rs +++ b/codegen/src/api/constants.rs @@ -12,13 +12,14 @@ use frame_metadata::{ }; use heck::ToSnakeCase as _; use proc_macro2::TokenStream as TokenStream2; -use proc_macro_error::abort_call_site; use quote::{ format_ident, quote, }; use scale_info::form::PortableForm; +use super::CodegenError; + /// Generate constants from the provided pallet's metadata. /// /// The function creates a new module named `constants` under the pallet's module. @@ -49,10 +50,10 @@ pub fn generate_constants( types_mod_ident: &syn::Ident, crate_path: &CratePath, should_gen_docs: bool, -) -> TokenStream2 { +) -> Result { // Early return if the pallet has no constants. if pallet.constants.is_empty() { - return quote!() + return Ok(quote!()) } let constants = &pallet.constants; @@ -60,8 +61,9 @@ pub fn generate_constants( let fn_name = format_ident!("{}", constant.name.to_snake_case()); let pallet_name = &pallet.name; let constant_name = &constant.name; - let constant_hash = subxt_metadata::get_constant_hash(metadata, pallet_name, constant_name) - .unwrap_or_else(|_| abort_call_site!("Metadata information for the constant {}_{} could not be found", pallet_name, constant_name)); + let Ok(constant_hash) = subxt_metadata::get_constant_hash(metadata, pallet_name, constant_name) else { + return Err(CodegenError::MissingConstantMetadata(constant_name.into(), pallet_name.into())); + }; let return_ty = type_gen.resolve_type_path(constant.ty.id()); let docs = &constant.docs; @@ -69,7 +71,7 @@ pub fn generate_constants( .then_some(quote! { #( #[doc = #docs ] )* }) .unwrap_or_default(); - quote! { + Ok(quote! { #docs pub fn #fn_name(&self) -> #crate_path::constants::StaticConstantAddress<#crate_path::metadata::DecodeStaticType<#return_ty>> { #crate_path::constants::StaticConstantAddress::new( @@ -78,10 +80,10 @@ pub fn generate_constants( [#(#constant_hash,)*] ) } - } - }); + }) + }).collect::, _>>()?; - quote! { + Ok(quote! { pub mod constants { use super::#types_mod_ident; @@ -91,5 +93,5 @@ pub fn generate_constants( #(#constant_fns)* } } - } + }) } diff --git a/codegen/src/api/events.rs b/codegen/src/api/events.rs index 95326059d9..4576dc7bea 100644 --- a/codegen/src/api/events.rs +++ b/codegen/src/api/events.rs @@ -11,6 +11,8 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; use scale_info::form::PortableForm; +use super::CodegenError; + /// Generate events from the provided pallet metadata. /// /// The function creates a new module named `events` under the pallet's module. @@ -46,12 +48,10 @@ pub fn generate_events( types_mod_ident: &syn::Ident, crate_path: &CratePath, should_gen_docs: bool, -) -> TokenStream2 { +) -> Result { // Early return if the pallet has no events. - let event = if let Some(ref event) = pallet.event { - event - } else { - return quote!() + let Some(event) = &pallet.event else { + return Ok(quote!()) }; let struct_defs = super::generate_structs_from_variants( @@ -61,7 +61,8 @@ pub fn generate_events( "Event", crate_path, should_gen_docs, - ); + )?; + let event_structs = struct_defs.iter().map(|(variant_name, struct_def)| { let pallet_name = &pallet.name; let event_struct = &struct_def.name; @@ -83,12 +84,12 @@ pub fn generate_events( .then_some(quote! { #( #[doc = #docs ] )* }) .unwrap_or_default(); - quote! { + Ok(quote! { #docs pub type Event = #event_type; pub mod events { use super::#types_mod_ident; #( #event_structs )* } - } + }) } diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index a1a4ce656c..eb4bbdd1de 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -22,6 +22,7 @@ use crate::{ }, utils::{ fetch_metadata_bytes_blocking, + FetchMetadataError, Uri, }, CratePath, @@ -33,8 +34,10 @@ use frame_metadata::{ RuntimeMetadataPrefixed, }; use heck::ToSnakeCase as _; -use proc_macro2::TokenStream as TokenStream2; -use proc_macro_error::abort_call_site; +use proc_macro2::{ + Span, + TokenStream as TokenStream2, +}; use quote::{ format_ident, quote, @@ -47,6 +50,67 @@ use std::{ }; use syn::parse_quote; +/// Error returned when the Codegen cannot generate the runtime API. +#[derive(Debug, thiserror::Error)] +pub enum CodegenError { + /// Cannot fetch the metadata bytes. + #[error("Failed to fetch metadata, make sure that you're pointing at a node which is providing V14 metadata: {0}")] + Fetch(#[from] FetchMetadataError), + /// Failed IO for the metadata file. + #[error("Failed IO for {0}, make sure that you are providing the correct file path for metadata V14: {1}")] + Io(String, std::io::Error), + /// Cannot decode the metadata bytes. + #[error("Could not decode metadata, only V14 metadata is supported: {0}")] + Decode(#[from] codec::Error), + /// Out of line modules are not supported. + #[error("Out-of-line subxt modules are not supported, make sure you are providing a body to your module: pub mod polkadot {{ ... }}")] + InvalidModule(Span), + /// Expected named or unnamed fields. + #[error("Fields should either be all named or all unnamed, make sure you are providing a valid metadata V14: {0}")] + InvalidFields(String), + /// Substitute types must have a valid path. + #[error("Substitute types must have a valid path")] + EmptySubstitutePath(Span), + /// Invalid type path. + #[error("Invalid type path {0}: {1}")] + InvalidTypePath(String, syn::Error), + /// Metadata for constant could not be found. + #[error("Metadata for constant entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")] + MissingConstantMetadata(String, String), + /// Metadata for storage could not be found. + #[error("Metadata for storage entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")] + MissingStorageMetadata(String, String), + /// StorageNMap should have N hashers. + #[error("Number of hashers ({0}) does not equal 1 for StorageMap, or match number of fields ({1}) for StorageNMap. Make sure you are providing a valid metadata V14")] + MismatchHashers(usize, usize), + /// Expected to find one hasher for StorageMap. + #[error("No hasher found for single key. Make sure you are providing a valid metadata V14")] + MissingHasher, + /// Metadata for call could not be found. + #[error("Metadata for call entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")] + MissingCallMetadata(String, String), + /// Call variant must have all named fields. + #[error("Call variant for type {0} must have all named fields. Make sure you are providing a valid metadata V14")] + InvalidCallVariant(u32), + /// Type should be an variant/enum. + #[error("{0} type should be an variant/enum type. Make sure you are providing a valid metadata V14")] + InvalidType(String), +} + +impl CodegenError { + /// Render the error as an invocation of syn::compile_error!. + pub fn into_compile_error(self) -> TokenStream2 { + let msg = self.to_string(); + let span = match self { + Self::InvalidModule(span) => span, + Self::EmptySubstitutePath(span) => span, + Self::InvalidTypePath(_, err) => err.span(), + _ => proc_macro2::Span::call_site(), + }; + syn::Error::new(span, msg).into_compile_error() + } +} + /// Generates the API for interacting with a Substrate runtime. /// /// # Arguments @@ -66,17 +130,15 @@ pub fn generate_runtime_api_from_path

( type_substitutes: TypeSubstitutes, crate_path: CratePath, should_gen_docs: bool, -) -> TokenStream2 +) -> Result where P: AsRef, { - let mut file = fs::File::open(&path).unwrap_or_else(|e| { - abort_call_site!("Failed to open {}: {}", path.as_ref().to_string_lossy(), e) - }); + let to_err = |err| CodegenError::Io(path.as_ref().to_string_lossy().into(), err); + let mut file = fs::File::open(&path).map_err(to_err)?; let mut bytes = Vec::new(); - file.read_to_end(&mut bytes) - .unwrap_or_else(|e| abort_call_site!("Failed to read metadata file: {}", e)); + file.read_to_end(&mut bytes).map_err(to_err)?; generate_runtime_api_from_bytes( item_mod, @@ -109,9 +171,8 @@ pub fn generate_runtime_api_from_url( type_substitutes: TypeSubstitutes, crate_path: CratePath, should_gen_docs: bool, -) -> TokenStream2 { - let bytes = fetch_metadata_bytes_blocking(url) - .unwrap_or_else(|e| abort_call_site!("Failed to obtain metadata: {}", e)); +) -> Result { + let bytes = fetch_metadata_bytes_blocking(url)?; generate_runtime_api_from_bytes( item_mod, @@ -142,9 +203,8 @@ pub fn generate_runtime_api_from_bytes( type_substitutes: TypeSubstitutes, crate_path: CratePath, should_gen_docs: bool, -) -> TokenStream2 { - let metadata = frame_metadata::RuntimeMetadataPrefixed::decode(&mut &bytes[..]) - .unwrap_or_else(|e| abort_call_site!("Failed to decode metadata: {}", e)); +) -> Result { + let metadata = frame_metadata::RuntimeMetadataPrefixed::decode(&mut &bytes[..])?; let generator = RuntimeGenerator::new(metadata); generator.generate_runtime( @@ -187,9 +247,9 @@ impl RuntimeGenerator { type_substitutes: TypeSubstitutes, crate_path: CratePath, should_gen_docs: bool, - ) -> TokenStream2 { + ) -> Result { let item_mod_attrs = item_mod.attrs.clone(); - let item_mod_ir = ir::ItemMod::from(item_mod); + let item_mod_ir = ir::ItemMod::try_from(item_mod)?; let default_derives = derives.default_derives(); let type_gen = TypeGenerator::new( @@ -200,7 +260,7 @@ impl RuntimeGenerator { crate_path.clone(), should_gen_docs, ); - let types_mod = type_gen.generate_types_mod(); + let types_mod = type_gen.generate_types_mod()?; let types_mod_ident = types_mod.ident(); let pallets_with_mod_names = self .metadata @@ -227,53 +287,56 @@ impl RuntimeGenerator { let metadata_hash = get_metadata_per_pallet_hash(&self.metadata, &pallet_names); - let modules = pallets_with_mod_names.iter().map(|(pallet, mod_name)| { - let calls = calls::generate_calls( - &self.metadata, - &type_gen, - pallet, - types_mod_ident, - &crate_path, - should_gen_docs, - ); + let modules = pallets_with_mod_names + .iter() + .map(|(pallet, mod_name)| { + let calls = calls::generate_calls( + &self.metadata, + &type_gen, + pallet, + types_mod_ident, + &crate_path, + should_gen_docs, + )?; - let event = events::generate_events( - &type_gen, - pallet, - types_mod_ident, - &crate_path, - should_gen_docs, - ); + let event = events::generate_events( + &type_gen, + pallet, + types_mod_ident, + &crate_path, + should_gen_docs, + )?; - let storage_mod = storage::generate_storage( - &self.metadata, - &type_gen, - pallet, - types_mod_ident, - &crate_path, - should_gen_docs, - ); + let storage_mod = storage::generate_storage( + &self.metadata, + &type_gen, + pallet, + types_mod_ident, + &crate_path, + should_gen_docs, + )?; - let constants_mod = constants::generate_constants( - &self.metadata, - &type_gen, - pallet, - types_mod_ident, - &crate_path, - should_gen_docs, - ); + let constants_mod = constants::generate_constants( + &self.metadata, + &type_gen, + pallet, + types_mod_ident, + &crate_path, + should_gen_docs, + )?; - quote! { - pub mod #mod_name { - use super::root_mod; - use super::#types_mod_ident; - #calls - #event - #storage_mod - #constants_mod - } - } - }); + Ok(quote! { + pub mod #mod_name { + use super::root_mod; + use super::#types_mod_ident; + #calls + #event + #storage_mod + #constants_mod + } + }) + }) + .collect::, CodegenError>>()?; let outer_event_variants = self.metadata.pallets.iter().filter_map(|p| { let variant_name = format_ident!("{}", p.name); @@ -319,7 +382,7 @@ impl RuntimeGenerator { let rust_items = item_mod_ir.rust_items(); - quote! { + Ok(quote! { #( #item_mod_attrs )* #[allow(dead_code, unused_imports, non_camel_case_types)] #[allow(clippy::all)] @@ -389,7 +452,7 @@ impl RuntimeGenerator { } } } - } + }) } } @@ -401,41 +464,42 @@ pub fn generate_structs_from_variants( error_message_type_name: &str, crate_path: &CratePath, should_gen_docs: bool, -) -> Vec<(String, CompositeDef)> +) -> Result, CodegenError> where F: Fn(&str) -> std::borrow::Cow, { let ty = type_gen.resolve_type(type_id); - if let scale_info::TypeDef::Variant(variant) = ty.type_def() { - variant - .variants() - .iter() - .map(|var| { - let struct_name = variant_to_struct_name(var.name()); - let fields = CompositeDefFields::from_scale_info_fields( - struct_name.as_ref(), - var.fields(), - &[], - type_gen, - ); - let docs = should_gen_docs.then_some(var.docs()).unwrap_or_default(); - let struct_def = CompositeDef::struct_def( - &ty, - struct_name.as_ref(), - Default::default(), - fields, - Some(parse_quote!(pub)), - type_gen, - docs, - crate_path, - ); - (var.name().to_string(), struct_def) - }) - .collect() - } else { - abort_call_site!( - "{} type should be an variant/enum type", - error_message_type_name - ) - } + + let scale_info::TypeDef::Variant(variant) = ty.type_def() else { + return Err(CodegenError::InvalidType(error_message_type_name.into())) + }; + + variant + .variants() + .iter() + .map(|var| { + let struct_name = variant_to_struct_name(var.name()); + + let fields = CompositeDefFields::from_scale_info_fields( + struct_name.as_ref(), + var.fields(), + &[], + type_gen, + )?; + + let docs = should_gen_docs.then_some(var.docs()).unwrap_or_default(); + let struct_def = CompositeDef::struct_def( + &ty, + struct_name.as_ref(), + Default::default(), + fields, + Some(parse_quote!(pub)), + type_gen, + docs, + crate_path, + )?; + + Ok((var.name.to_string(), struct_def)) + }) + .collect() } diff --git a/codegen/src/api/storage.rs b/codegen/src/api/storage.rs index 9834246ab7..69596249f6 100644 --- a/codegen/src/api/storage.rs +++ b/codegen/src/api/storage.rs @@ -16,7 +16,6 @@ use frame_metadata::{ }; use heck::ToSnakeCase as _; use proc_macro2::TokenStream as TokenStream2; -use proc_macro_error::abort_call_site; use quote::{ format_ident, quote, @@ -26,6 +25,8 @@ use scale_info::{ TypeDef, }; +use super::CodegenError; + /// Generate functions which create storage addresses from the provided pallet's metadata. /// These addresses can be used to access and iterate over storage values. /// @@ -42,14 +43,12 @@ pub fn generate_storage( types_mod_ident: &syn::Ident, crate_path: &CratePath, should_gen_docs: bool, -) -> TokenStream2 { - let storage = if let Some(ref storage) = pallet.storage { - storage - } else { - return quote!() +) -> Result { + let Some(storage) = &pallet.storage else { + return Ok(quote!()) }; - let storage_fns: Vec<_> = storage + let storage_fns = storage .entries .iter() .map(|entry| { @@ -62,9 +61,9 @@ pub fn generate_storage( should_gen_docs, ) }) - .collect(); + .collect::, CodegenError>>()?; - quote! { + Ok(quote! { pub mod storage { use super::#types_mod_ident; @@ -74,7 +73,7 @@ pub fn generate_storage( #( #storage_fns )* } } - } + }) } fn generate_storage_entry_fns( @@ -84,7 +83,7 @@ fn generate_storage_entry_fns( storage_entry: &StorageEntryMetadata, crate_path: &CratePath, should_gen_docs: bool, -) -> TokenStream2 { +) -> Result { let (fields, key_impl) = match storage_entry.ty { StorageEntryType::Plain(_) => (vec![], quote!(vec![])), StorageEntryType::Map { @@ -146,13 +145,10 @@ fn generate_storage_entry_fns( vec![ #crate_path::storage::address::StorageMapKey::new(&(#( #items.borrow() ),*), #hasher) ] } } else { - // If we hit this condition, we don't know how to handle the number of hashes vs fields - // that we've been handed, so abort. - abort_call_site!( - "Number of hashers ({}) does not equal 1 for StorageMap, or match number of fields ({}) for StorageNMap", + return Err(CodegenError::MismatchHashers( hashers.len(), - fields.len() - ) + fields.len(), + )) }; (fields, key_impl) @@ -160,9 +156,9 @@ fn generate_storage_entry_fns( _ => { let ty_path = type_gen.resolve_type_path(key.id()); let fields = vec![(format_ident!("_0"), ty_path)]; - let hasher = hashers.get(0).unwrap_or_else(|| { - abort_call_site!("No hasher found for single key") - }); + let Some(hasher) = hashers.get(0) else { + return Err(CodegenError::MissingHasher) + }; let key_impl = quote! { vec![ #crate_path::storage::address::StorageMapKey::new(_0.borrow(), #hasher) ] }; @@ -174,15 +170,14 @@ fn generate_storage_entry_fns( let pallet_name = &pallet.name; let storage_name = &storage_entry.name; - let storage_hash = - subxt_metadata::get_storage_hash(metadata, pallet_name, storage_name) - .unwrap_or_else(|_| { - abort_call_site!( - "Metadata information for the storage entry {}_{} could not be found", - pallet_name, - storage_name - ) - }); + let storage_hash = subxt_metadata::get_storage_hash( + metadata, + pallet_name, + storage_name, + ) + .map_err(|_| { + CodegenError::MissingStorageMetadata(pallet_name.into(), storage_name.into()) + })?; let fn_name = format_ident!("{}", storage_entry.name.to_snake_case()); let storage_entry_ty = match storage_entry.ty { @@ -251,7 +246,7 @@ fn generate_storage_entry_fns( quote!() }; - quote! { + Ok(quote! { // Access a specific value from a storage entry #docs pub fn #fn_name( @@ -267,5 +262,5 @@ fn generate_storage_entry_fns( } #root_entry_fn - } + }) } diff --git a/codegen/src/ir.rs b/codegen/src/ir.rs index 7c6131bf7c..ad730ff789 100644 --- a/codegen/src/ir.rs +++ b/codegen/src/ir.rs @@ -2,7 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use proc_macro_error::abort; +use crate::api::CodegenError; use syn::token; #[derive(Debug, PartialEq, Eq)] @@ -14,22 +14,22 @@ pub struct ItemMod { items: Vec, } -impl From for ItemMod { - fn from(module: syn::ItemMod) -> Self { +impl TryFrom for ItemMod { + type Error = CodegenError; + + fn try_from(module: syn::ItemMod) -> Result { let (brace, items) = match module.content { Some((brace, items)) => (brace, items), - None => { - abort!(module, "out-of-line subxt modules are not supported",) - } + None => return Err(CodegenError::InvalidModule(module.ident.span())), }; - Self { + Ok(Self { vis: module.vis, mod_token: module.mod_token, ident: module.ident, brace, items, - } + }) } } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index ba22b60cf2..de1b569492 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -39,7 +39,7 @@ //! let generator = subxt_codegen::RuntimeGenerator::new(metadata); //! // Include metadata documentation in the Runtime API. //! let generate_docs = true; -//! let runtime_api = generator.generate_runtime(item_mod, derives, substs, CratePath::default(), generate_docs); +//! let runtime_api = generator.generate_runtime(item_mod, derives, substs, CratePath::default(), generate_docs).unwrap(); //! println!("{}", runtime_api); //! ``` @@ -56,6 +56,7 @@ pub use self::{ generate_runtime_api_from_bytes, generate_runtime_api_from_path, generate_runtime_api_from_url, + CodegenError, RuntimeGenerator, }, types::{ diff --git a/codegen/src/types/composite_def.rs b/codegen/src/types/composite_def.rs index 7d6af13af4..8226883a11 100644 --- a/codegen/src/types/composite_def.rs +++ b/codegen/src/types/composite_def.rs @@ -2,6 +2,8 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use crate::api::CodegenError; + use super::{ CratePath, Derives, @@ -12,7 +14,6 @@ use super::{ TypePath, }; use proc_macro2::TokenStream; -use proc_macro_error::abort_call_site; use quote::{ format_ident, quote, @@ -52,8 +53,8 @@ impl CompositeDef { type_gen: &TypeGenerator, docs: &[String], crate_path: &CratePath, - ) -> Self { - let mut derives = type_gen.type_derives(ty); + ) -> Result { + let mut derives = type_gen.type_derives(ty)?; let fields: Vec<_> = fields_def.field_types().collect(); if fields.len() == 1 { @@ -84,7 +85,7 @@ impl CompositeDef { let name = format_ident!("{}", ident); let docs_token = Some(quote! { #( #[doc = #docs ] )* }); - Self { + Ok(Self { name, kind: CompositeDefKind::Struct { derives, @@ -93,7 +94,7 @@ impl CompositeDef { }, fields: fields_def, docs: docs_token, - } + }) } /// Construct a definition which will generate code for an `enum` variant. @@ -183,9 +184,9 @@ impl CompositeDefFields { fields: &[Field], parent_type_params: &[TypeParameter], type_gen: &TypeGenerator, - ) -> Self { + ) -> Result { if fields.is_empty() { - return Self::NoFields + return Ok(Self::NoFields) } let mut named_fields = Vec::new(); @@ -209,17 +210,15 @@ impl CompositeDefFields { } if !named_fields.is_empty() && !unnamed_fields.is_empty() { - abort_call_site!( - "'{}': Fields should either be all named or all unnamed.", - name, - ) + return Err(CodegenError::InvalidFields(name.into())) } - if !named_fields.is_empty() { + let res = if !named_fields.is_empty() { Self::Named(named_fields) } else { Self::Unnamed(unnamed_fields) - } + }; + Ok(res) } /// Returns the set of composite fields. diff --git a/codegen/src/types/mod.rs b/codegen/src/types/mod.rs index eb40e9f3f3..d2cea5c173 100644 --- a/codegen/src/types/mod.rs +++ b/codegen/src/types/mod.rs @@ -17,7 +17,6 @@ use proc_macro2::{ Span, TokenStream, }; -use proc_macro_error::abort_call_site; use quote::{ quote, ToTokens, @@ -30,6 +29,8 @@ use scale_info::{ }; use std::collections::BTreeMap; +use crate::api::CodegenError; + pub use self::{ composite_def::{ CompositeDef, @@ -91,7 +92,7 @@ impl<'a> TypeGenerator<'a> { } /// Generate a module containing all types defined in the supplied type registry. - pub fn generate_types_mod(&self) -> Module { + pub fn generate_types_mod(&self) -> Result { let root_mod_ident = &self.types_mod_ident; let mut root_mod = Module::new(root_mod_ident.clone(), root_mod_ident.clone()); @@ -127,11 +128,11 @@ impl<'a> TypeGenerator<'a> { self, &self.crate_path, self.should_gen_docs, - ), + )?, ); } - root_mod + Ok(root_mod) } /// # Panics @@ -311,12 +312,11 @@ impl<'a> TypeGenerator<'a> { } /// Returns the derives to be applied to a generated type. - pub fn type_derives(&self, ty: &Type) -> Derives { + pub fn type_derives(&self, ty: &Type) -> Result { let joined_path = ty.path().segments().join("::"); - let ty_path: syn::TypePath = syn::parse_str(&joined_path).unwrap_or_else(|e| { - abort_call_site!("'{}' is an invalid type path: {:?}", joined_path, e,) - }); - self.derives.resolve(&ty_path) + let ty_path: syn::TypePath = syn::parse_str(&joined_path) + .map_err(|e| CodegenError::InvalidTypePath(joined_path, e))?; + Ok(self.derives.resolve(&ty_path)) } } diff --git a/codegen/src/types/substitutes.rs b/codegen/src/types/substitutes.rs index 3cd8d5f54a..c05f1e1883 100644 --- a/codegen/src/types/substitutes.rs +++ b/codegen/src/types/substitutes.rs @@ -2,8 +2,10 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::CratePath; -use proc_macro_error::abort; +use crate::{ + api::CodegenError, + CratePath, +}; use std::{ borrow::Cow, collections::HashMap, @@ -106,39 +108,48 @@ impl TypeSubstitutes { } } - pub fn extend(&mut self, elems: impl IntoIterator) { - self.substitutes - .extend(elems.into_iter().map(|(path, AbsolutePath(mut with))| { - let Some(syn::PathSegment { arguments: src_path_args, ..}) = path.segments.last() else { abort!(path.span(), "Empty path") }; - let Some(syn::PathSegment { arguments: target_path_args, ..}) = with.segments.last_mut() else { abort!(with.span(), "Empty path") }; + pub fn extend( + &mut self, + elems: impl IntoIterator, + ) -> Result<(), CodegenError> { + let to_extend = elems.into_iter().map(|(path, AbsolutePath(mut with))| { + let Some(syn::PathSegment { arguments: src_path_args, ..}) = path.segments.last() else { + return Err(CodegenError::EmptySubstitutePath(path.span())) + }; + let Some(syn::PathSegment { arguments: target_path_args, ..}) = with.segments.last_mut() else { + return Err(CodegenError::EmptySubstitutePath(with.span())) + }; - let source_args: Vec<_> = type_args(src_path_args).collect(); + let source_args: Vec<_> = type_args(src_path_args).collect(); - let param_mapping = if source_args.is_empty() { - // If the type parameters on the source type are not specified, then this means that - // the type is either not generic or the user wants to pass through all the parameters - TypeParamMapping::None - } else { - // Describe the mapping in terms of "which source param idx is used for each target param". - // So, for each target param, find the matching source param index. - let mapping = type_args(target_path_args) - .filter_map(|arg| - source_args - .iter() - .position(|&src| src == arg) - .map(|src_idx| - u8::try_from(src_idx).expect("type arguments to be fewer than 256; qed"), - ) - ).collect(); - TypeParamMapping::Specified(mapping) - }; + let param_mapping = if source_args.is_empty() { + // If the type parameters on the source type are not specified, then this means that + // the type is either not generic or the user wants to pass through all the parameters + TypeParamMapping::None + } else { + // Describe the mapping in terms of "which source param idx is used for each target param". + // So, for each target param, find the matching source param index. + let mapping = type_args(target_path_args) + .filter_map(|arg| + source_args + .iter() + .position(|&src| src == arg) + .map(|src_idx| + u8::try_from(src_idx).expect("type arguments to be fewer than 256; qed"), + ) + ).collect(); + TypeParamMapping::Specified(mapping) + }; - // NOTE: Params are late bound and held separately, so clear them - // here to not mess pretty printing this path and params together - *target_path_args = syn::PathArguments::None; + // NOTE: Params are late bound and held separately, so clear them + // here to not mess pretty printing this path and params together + *target_path_args = syn::PathArguments::None; - (PathSegments::from(&path), Substitute { path: with, param_mapping }) - })); + Ok((PathSegments::from(&path), Substitute { path: with, param_mapping })) + }).collect::, _>>()?; + + self.substitutes.extend(to_extend); + Ok(()) } /// Given a source type path, return a substituted type path if a substitution is defined. diff --git a/codegen/src/types/tests.rs b/codegen/src/types/tests.rs index 24b92f8905..5980ce3aeb 100644 --- a/codegen/src/types/tests.rs +++ b/codegen/src/types/tests.rs @@ -48,7 +48,7 @@ fn generate_struct_with_primitives() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -97,7 +97,7 @@ fn generate_struct_with_a_struct_field() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -145,7 +145,7 @@ fn generate_tuple_struct() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -230,7 +230,7 @@ fn derive_compact_as_for_uint_wrapper_structs() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -297,7 +297,7 @@ fn generate_enum() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -358,7 +358,7 @@ fn compact_fields() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -417,7 +417,7 @@ fn compact_generic_parameter() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -461,7 +461,7 @@ fn generate_array_field() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -501,7 +501,7 @@ fn option_fields() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -544,7 +544,7 @@ fn box_fields_struct() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -587,7 +587,7 @@ fn box_fields_enum() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -630,7 +630,7 @@ fn range_fields() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -677,7 +677,7 @@ fn generics() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -728,7 +728,7 @@ fn generics_nested() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -782,7 +782,7 @@ fn generate_bitvec() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -838,7 +838,7 @@ fn generics_with_alias_adds_phantom_data_marker() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -901,7 +901,7 @@ fn modules() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -961,7 +961,7 @@ fn dont_force_struct_names_camel_case() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -1005,7 +1005,7 @@ fn apply_user_defined_derives_for_all_types() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( @@ -1073,7 +1073,7 @@ fn apply_user_defined_derives_for_specific_types() { crate_path, true, ); - let types = type_gen.generate_types_mod(); + let types = type_gen.generate_types_mod().expect("Valid type mod; qed"); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); assert_eq!( diff --git a/codegen/src/types/type_def.rs b/codegen/src/types/type_def.rs index e4f20422fe..2cc669a2d3 100644 --- a/codegen/src/types/type_def.rs +++ b/codegen/src/types/type_def.rs @@ -2,6 +2,8 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use crate::api::CodegenError; + use super::{ CompositeDef, CompositeDefFields, @@ -46,8 +48,8 @@ impl TypeDefGen { type_gen: &TypeGenerator, crate_path: &CratePath, should_gen_docs: bool, - ) -> Self { - let derives = type_gen.type_derives(ty); + ) -> Result { + let derives = type_gen.type_derives(ty)?; let type_params = ty .type_params() @@ -78,7 +80,7 @@ impl TypeDefGen { composite.fields(), type_params.params(), type_gen, - ); + )?; type_params.update_unused(fields.field_types()); let docs = should_gen_docs.then_some(ty.docs()).unwrap_or_default(); let composite_def = CompositeDef::struct_def( @@ -90,11 +92,12 @@ impl TypeDefGen { type_gen, docs, crate_path, - ); + )?; TypeDefGenKind::Struct(composite_def) } TypeDef::Variant(variant) => { let type_name = ty.path().ident().expect("variants should have a name"); + let variants = variant .variants() .iter() @@ -104,15 +107,15 @@ impl TypeDefGen { v.fields(), type_params.params(), type_gen, - ); + )?; type_params.update_unused(fields.field_types()); let docs = should_gen_docs.then_some(v.docs()).unwrap_or_default(); let variant_def = CompositeDef::enum_variant_def(v.name(), fields, docs); - (v.index(), variant_def) + Ok((v.index(), variant_def)) }) - .collect(); + .collect::, CodegenError>>()?; TypeDefGenKind::Enum(type_name, variants) } @@ -124,12 +127,12 @@ impl TypeDefGen { .then_some(quote! { #( #[doc = #docs ] )* }) .unwrap_or_default(); - Self { + Ok(Self { type_params, derives, ty_kind, ty_docs, - } + }) } } diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 8cde54f7b8..95dac9597d 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -180,7 +180,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { } let mut type_substitutes = TypeSubstitutes::new(&crate_path); - type_substitutes.extend(args.substitute_type.into_iter().map( + if let Err(err) = type_substitutes.extend(args.substitute_type.into_iter().map( |SubstituteType { ty, with }| { ( ty, @@ -190,7 +190,9 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { }), ) }, - )); + )) { + return err.into_compile_error().into() + } let should_gen_docs = args.generate_docs.is_present(); match (args.runtime_metadata_path, args.runtime_metadata_url) { @@ -206,7 +208,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { crate_path, should_gen_docs, ) - .into() + .map_or_else(|err| err.into_compile_error().into(), Into::into) } (None, Some(url_string)) => { let url = Uri::from_str(&url_string).unwrap_or_else(|_| { @@ -220,7 +222,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { crate_path, should_gen_docs, ) - .into() + .map_or_else(|err| err.into_compile_error().into(), Into::into) } (None, None) => { abort_call_site!("One of 'runtime_metadata_path' or 'runtime_metadata_url' must be provided") diff --git a/testing/integration-tests/src/codegen/codegen_documentation.rs b/testing/integration-tests/src/codegen/codegen_documentation.rs index dad76dbf29..4080dc9edf 100644 --- a/testing/integration-tests/src/codegen/codegen_documentation.rs +++ b/testing/integration-tests/src/codegen/codegen_documentation.rs @@ -66,6 +66,7 @@ fn generate_runtime_interface(crate_path: CratePath, should_gen_docs: bool) -> S crate_path, should_gen_docs, ) + .expect("API generation must be valid") .to_string() } @@ -155,6 +156,7 @@ fn check_root_attrs_preserved() { CratePath::default(), true, ) + .expect("API generation must be valid") .to_string(); let doc_str_loc = generated_code