From 398ca02049233c3d7e3629f8ed0a1e98be90c064 Mon Sep 17 00:00:00 2001 From: hal3e Date: Thu, 25 Apr 2024 17:36:59 +0200 Subject: [PATCH 1/7] feat: improve error on malformed `abi` --- .../src/program_bindings/abigen/abigen_target.rs | 8 +++++++- .../fuels-macros/tests/ui/abigen/malformed_abi_error.rs | 5 +++++ .../tests/ui/abigen/malformed_abi_error.stderr | 7 +++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 packages/fuels-macros/tests/ui/abigen/malformed_abi_error.rs create mode 100644 packages/fuels-macros/tests/ui/abigen/malformed_abi_error.stderr diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs index 1fd8649a4..a4e26e27c 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs @@ -43,8 +43,14 @@ fn parse_program_abi(abi_source: &str) -> Result { let source = Source::parse(abi_source).expect("failed to parse JSON ABI"); let json_abi_str = source.get().expect("failed to parse JSON ABI from string"); - let abi = FullProgramABI::from_json_abi(&json_abi_str)?; + let abi = FullProgramABI::from_json_abi(&json_abi_str).map_err(|e| { + error!( + "{} {}", + "malformed `abi`. Did you use `forc` to create it?: ", e + ) + })?; let path = source.path(); + Ok(Abi { path, abi }) } diff --git a/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.rs b/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.rs new file mode 100644 index 000000000..7397d6c57 --- /dev/null +++ b/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.rs @@ -0,0 +1,5 @@ +use fuels_macros::abigen; + +abigen!(Contract(name = "SomeName", abi = r#"{}"#)); + +fn main() {} diff --git a/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.stderr b/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.stderr new file mode 100644 index 000000000..79a6852e9 --- /dev/null +++ b/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.stderr @@ -0,0 +1,7 @@ +error: proc macro panicked + --> tests/ui/abigen/malformed_abi_error.rs:3:1 + | +3 | abigen!(Contract(name = "SomeName", abi = r#"{}"#)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: called `Result::unwrap()` on an `Err` value: "malformed `abi`. Did you use `forc` to create it?: missing field `types` at line 1 column 2" From 5595b52b87d06ff5d11c80c9f2facf76b68bfac0 Mon Sep 17 00:00:00 2001 From: hal3e Date: Fri, 26 Apr 2024 11:43:13 +0200 Subject: [PATCH 2/7] refactor abi_target --- packages/fuels-macros/src/abigen/parsing.rs | 15 ++++--- packages/fuels-macros/src/lib.rs | 8 +++- .../src/setup_program_test/code_gen.rs | 42 +++++++++++++------ .../tests/ui/abigen/invalid_abi_path.rs | 5 +++ .../tests/ui/abigen/invalid_abi_path.stderr | 5 +++ ...alformed_abi_error.rs => malformed_abi.rs} | 0 .../tests/ui/abigen/malformed_abi.stderr | 5 +++ .../ui/abigen/malformed_abi_error.stderr | 7 ---- .../duplicate_wallet_command.rs | 2 +- .../duplicate_wallet_names.rs | 2 +- .../ui/setup_program_test/invalid_path.rs | 8 ++++ .../ui/setup_program_test/invalid_path.stderr | 5 +++ .../invalid_project_path.rs | 5 +++ .../invalid_project_path.stderr | 5 +++ 14 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 packages/fuels-macros/tests/ui/abigen/invalid_abi_path.rs create mode 100644 packages/fuels-macros/tests/ui/abigen/invalid_abi_path.stderr rename packages/fuels-macros/tests/ui/abigen/{malformed_abi_error.rs => malformed_abi.rs} (100%) create mode 100644 packages/fuels-macros/tests/ui/abigen/malformed_abi.stderr delete mode 100644 packages/fuels-macros/tests/ui/abigen/malformed_abi_error.stderr create mode 100644 packages/fuels-macros/tests/ui/setup_program_test/invalid_path.rs create mode 100644 packages/fuels-macros/tests/ui/setup_program_test/invalid_path.stderr create mode 100644 packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.rs create mode 100644 packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.stderr diff --git a/packages/fuels-macros/src/abigen/parsing.rs b/packages/fuels-macros/src/abigen/parsing.rs index 991d14f46..50ef012a4 100644 --- a/packages/fuels-macros/src/abigen/parsing.rs +++ b/packages/fuels-macros/src/abigen/parsing.rs @@ -1,4 +1,4 @@ -use fuels_code_gen::{AbigenTarget, ProgramType}; +use fuels_code_gen::{utils::parse_program_abi, Abi, AbigenTarget, ProgramType}; use syn::{ parse::{Parse, ParseStream}, Result, @@ -16,7 +16,7 @@ impl From for AbigenTarget { fn from(macro_target: MacroAbigenTarget) -> Self { AbigenTarget { name: macro_target.name, - abi: macro_target.abi, + source: macro_target.source, program_type: macro_target.program_type, } } @@ -27,8 +27,8 @@ impl From for AbigenTarget { #[derive(Debug)] pub(crate) struct MacroAbigenTarget { pub(crate) name: String, - pub(crate) abi: String, - pub(crate) program_type: ProgramType, + pub(crate) source: Abi, + pub program_type: ProgramType, } pub(crate) struct MacroAbigenTargets { @@ -54,11 +54,14 @@ impl MacroAbigenTarget { name_values.validate_has_no_other_names(&["name", "abi"])?; let name = name_values.get_as_lit_str("name")?.value(); - let abi = name_values.get_as_lit_str("abi")?.value(); + let abi_lit_str = name_values.get_as_lit_str("abi")?; + + let source = parse_program_abi(&abi_lit_str.value()) + .map_err(|e| syn::Error::new(abi_lit_str.span(), e.to_string()))?; Ok(Self { name, - abi, + source, program_type, }) } diff --git a/packages/fuels-macros/src/lib.rs b/packages/fuels-macros/src/lib.rs index 237628446..9711dd752 100644 --- a/packages/fuels-macros/src/lib.rs +++ b/packages/fuels-macros/src/lib.rs @@ -36,14 +36,18 @@ mod setup_program_test; pub fn abigen(input: TokenStream) -> TokenStream { let targets = parse_macro_input!(input as MacroAbigenTargets); - Abigen::generate(targets.into(), false).unwrap().into() + Abigen::generate(targets.into(), false) + .expect("abigen generation failed") + .into() } #[proc_macro] pub fn wasm_abigen(input: TokenStream) -> TokenStream { let targets = parse_macro_input!(input as MacroAbigenTargets); - Abigen::generate(targets.into(), true).unwrap().into() + Abigen::generate(targets.into(), true) + .expect("abigen generation failed") + .into() } /// Used to reduce boilerplate in integration tests. diff --git a/packages/fuels-macros/src/setup_program_test/code_gen.rs b/packages/fuels-macros/src/setup_program_test/code_gen.rs index 9b528e238..63fe1b1b1 100644 --- a/packages/fuels-macros/src/setup_program_test/code_gen.rs +++ b/packages/fuels-macros/src/setup_program_test/code_gen.rs @@ -3,8 +3,11 @@ use std::{ path::{Path, PathBuf}, }; -use fuels_code_gen::{utils::ident, Abigen, AbigenTarget, ProgramType}; -use proc_macro2::{Ident, TokenStream}; +use fuels_code_gen::{ + utils::{ident, parse_program_abi}, + Abigen, AbigenTarget, ProgramType, +}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::LitStr; @@ -24,7 +27,7 @@ pub(crate) fn generate_setup_program_test_code( } = commands; let project_lookup = generate_project_lookup(&generate_bindings)?; - let abigen_code = abigen_code(&project_lookup); + let abigen_code = abigen_code(&project_lookup)?; let wallet_code = wallet_initialization_code(initialize_wallets); let deploy_code = contract_deploying_code(&deploy_contract, &project_lookup); let script_code = script_loading_code(&load_scripts, &project_lookup); @@ -50,18 +53,26 @@ fn generate_project_lookup(commands: &AbigenCommand) -> syn::Result) -> TokenStream { - let targets = generate_abigen_targets(project_lookup); - Abigen::generate(targets, false).expect("Failed to generate abigen") +fn abigen_code(project_lookup: &HashMap) -> syn::Result { + let targets = parse_abigen_targets(project_lookup)?; + + Ok(Abigen::generate(targets, false).expect("abigen generation failed")) } -fn generate_abigen_targets(project_lookup: &HashMap) -> Vec { +fn parse_abigen_targets( + project_lookup: &HashMap, +) -> syn::Result> { project_lookup .iter() - .map(|(name, project)| AbigenTarget { - name: name.clone(), - abi: project.abi_path(), - program_type: project.program_type, + .map(|(name, project)| { + let source = parse_program_abi(&project.abi_path()) + .map_err(|e| syn::Error::new(project.path_span, e.to_string()))?; + + Ok(AbigenTarget { + name: name.clone(), + source, + program_type: project.program_type, + }) }) .collect() } @@ -181,6 +192,7 @@ fn script_loading_code( struct Project { program_type: ProgramType, path: PathBuf, + path_span: Span, } impl Project { @@ -188,11 +200,15 @@ impl Project { let path = Path::new(&dir.value()).canonicalize().map_err(|_| { syn::Error::new_spanned( dir.clone(), - "Unable to canonicalize forc project path. Make sure the path is valid!", + "unable to canonicalize forc project path. Make sure the path is valid!", ) })?; - Ok(Self { program_type, path }) + Ok(Self { + program_type, + path, + path_span: dir.span(), + }) } fn compile_file_path(&self, suffix: &str, description: &str) -> String { diff --git a/packages/fuels-macros/tests/ui/abigen/invalid_abi_path.rs b/packages/fuels-macros/tests/ui/abigen/invalid_abi_path.rs new file mode 100644 index 000000000..d069c48eb --- /dev/null +++ b/packages/fuels-macros/tests/ui/abigen/invalid_abi_path.rs @@ -0,0 +1,5 @@ +use fuels_macros::abigen; + +abigen!(Contract(name = "SomeName", abi = "some_abi.json")); + +fn main() {} diff --git a/packages/fuels-macros/tests/ui/abigen/invalid_abi_path.stderr b/packages/fuels-macros/tests/ui/abigen/invalid_abi_path.stderr new file mode 100644 index 000000000..dbf6fe6fd --- /dev/null +++ b/packages/fuels-macros/tests/ui/abigen/invalid_abi_path.stderr @@ -0,0 +1,5 @@ +error: failed to read `abi` file with path $WORKSPACE/target/tests/trybuild/fuels-macros/some_abi.json: No such file or directory (os error 2) + --> tests/ui/abigen/invalid_abi_path.rs:3:43 + | +3 | abigen!(Contract(name = "SomeName", abi = "some_abi.json")); + | ^^^^^^^^^^^^^^^ diff --git a/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.rs b/packages/fuels-macros/tests/ui/abigen/malformed_abi.rs similarity index 100% rename from packages/fuels-macros/tests/ui/abigen/malformed_abi_error.rs rename to packages/fuels-macros/tests/ui/abigen/malformed_abi.rs diff --git a/packages/fuels-macros/tests/ui/abigen/malformed_abi.stderr b/packages/fuels-macros/tests/ui/abigen/malformed_abi.stderr new file mode 100644 index 000000000..ea10a8867 --- /dev/null +++ b/packages/fuels-macros/tests/ui/abigen/malformed_abi.stderr @@ -0,0 +1,5 @@ +error: malformed `abi`. Did you use `forc` to create it?: missing field `types` at line 1 column 2 + --> tests/ui/abigen/malformed_abi.rs:3:43 + | +3 | abigen!(Contract(name = "SomeName", abi = r#"{}"#)); + | ^^^^^^^ diff --git a/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.stderr b/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.stderr deleted file mode 100644 index 79a6852e9..000000000 --- a/packages/fuels-macros/tests/ui/abigen/malformed_abi_error.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: proc macro panicked - --> tests/ui/abigen/malformed_abi_error.rs:3:1 - | -3 | abigen!(Contract(name = "SomeName", abi = r#"{}"#)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: message: called `Result::unwrap()` on an `Err` value: "malformed `abi`. Did you use `forc` to create it?: missing field `types` at line 1 column 2" diff --git a/packages/fuels-macros/tests/ui/setup_program_test/duplicate_wallet_command.rs b/packages/fuels-macros/tests/ui/setup_program_test/duplicate_wallet_command.rs index 3710fd646..4fa2f78c5 100644 --- a/packages/fuels-macros/tests/ui/setup_program_test/duplicate_wallet_command.rs +++ b/packages/fuels-macros/tests/ui/setup_program_test/duplicate_wallet_command.rs @@ -3,7 +3,7 @@ use fuels_macros::setup_program_test; setup_program_test!( Wallets("wallet1"), Wallets("wallet2"), - Abigen(Contract(name = "MyContract", project = "some_file.json")) + Abigen(Contract(name = "MyContract", project = "some_project")) ); fn main() {} diff --git a/packages/fuels-macros/tests/ui/setup_program_test/duplicate_wallet_names.rs b/packages/fuels-macros/tests/ui/setup_program_test/duplicate_wallet_names.rs index 346164f32..a39d621e0 100644 --- a/packages/fuels-macros/tests/ui/setup_program_test/duplicate_wallet_names.rs +++ b/packages/fuels-macros/tests/ui/setup_program_test/duplicate_wallet_names.rs @@ -2,7 +2,7 @@ use fuels_macros::setup_program_test; setup_program_test!( Wallets("wallet1", "wallet1"), - Abigen(Contract(name = "MyContract", project = "some_file.json")) + Abigen(Contract(name = "MyContract", project = "some_project")) ); fn main() {} diff --git a/packages/fuels-macros/tests/ui/setup_program_test/invalid_path.rs b/packages/fuels-macros/tests/ui/setup_program_test/invalid_path.rs new file mode 100644 index 000000000..f24a39f08 --- /dev/null +++ b/packages/fuels-macros/tests/ui/setup_program_test/invalid_path.rs @@ -0,0 +1,8 @@ +use fuels_macros::setup_program_test; + +setup_program_test!(Abigen(Contract( + name = "MyContract", + project = "some_project" +))); + +fn main() {} diff --git a/packages/fuels-macros/tests/ui/setup_program_test/invalid_path.stderr b/packages/fuels-macros/tests/ui/setup_program_test/invalid_path.stderr new file mode 100644 index 000000000..53dec148f --- /dev/null +++ b/packages/fuels-macros/tests/ui/setup_program_test/invalid_path.stderr @@ -0,0 +1,5 @@ +error: unable to canonicalize forc project path. Make sure the path is valid! + --> tests/ui/setup_program_test/invalid_path.rs:5:15 + | +5 | project = "some_project" + | ^^^^^^^^^^^^^^ diff --git a/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.rs b/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.rs new file mode 100644 index 000000000..730515260 --- /dev/null +++ b/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.rs @@ -0,0 +1,5 @@ +use fuels_macros::setup_program_test; + +setup_program_test!(Abigen(Contract(name = "MyContract", project = "."))); + +fn main() {} diff --git a/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.stderr b/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.stderr new file mode 100644 index 000000000..27bde8e8a --- /dev/null +++ b/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.stderr @@ -0,0 +1,5 @@ +error: failed to read `abi` file with path $WORKSPACE/target/tests/trybuild/fuels-macros/out/debug/fuels-macros-abi.json: No such file or directory (os error 2) + --> tests/ui/setup_program_test/invalid_project_path.rs:3:68 + | +3 | setup_program_test!(Abigen(Contract(name = "MyContract", project = "."))); + | ^^^ From 3b07894c709eff214beda60f8bc5d1584cee8ee2 Mon Sep 17 00:00:00 2001 From: hal3e Date: Fri, 26 Apr 2024 11:43:50 +0200 Subject: [PATCH 3/7] refactor abi_target --- .../fuels-code-gen/src/program_bindings.rs | 2 +- .../src/program_bindings/abigen.rs | 27 +++-------- .../program_bindings/abigen/abigen_target.rs | 46 ++----------------- .../src/program_bindings/abigen/bindings.rs | 4 +- .../program_bindings/abigen/bindings/utils.rs | 4 +- packages/fuels-code-gen/src/utils.rs | 14 ++++++ packages/fuels-code-gen/src/utils/source.rs | 35 +++++++++----- 7 files changed, 54 insertions(+), 78 deletions(-) diff --git a/packages/fuels-code-gen/src/program_bindings.rs b/packages/fuels-code-gen/src/program_bindings.rs index 6e7ac5d34..240013408 100644 --- a/packages/fuels-code-gen/src/program_bindings.rs +++ b/packages/fuels-code-gen/src/program_bindings.rs @@ -4,4 +4,4 @@ mod generated_code; mod resolved_type; mod utils; -pub use abigen::{Abigen, AbigenTarget, ProgramType}; +pub use abigen::{Abi, Abigen, AbigenTarget, ProgramType}; diff --git a/packages/fuels-code-gen/src/program_bindings/abigen.rs b/packages/fuels-code-gen/src/program_bindings/abigen.rs index 16fe42941..304cf2910 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, path::PathBuf}; -pub use abigen_target::{AbigenTarget, ProgramType}; +pub use abigen_target::{Abi, AbigenTarget, ProgramType}; use fuel_abi_types::abi::full_program::FullTypeDeclaration; use inflector::Inflector; use itertools::Itertools; @@ -11,8 +11,7 @@ use regex::Regex; use crate::{ error::Result, program_bindings::{ - abigen::{abigen_target::ParsedAbigenTarget, bindings::generate_bindings}, - custom_types::generate_types, + abigen::bindings::generate_bindings, custom_types::generate_types, generated_code::GeneratedCode, }, utils::ident, @@ -35,9 +34,7 @@ impl Abigen { /// for, and of what nature (Contract, Script or Predicate). /// * `no_std`: don't use the Rust std library. pub fn generate(targets: Vec, no_std: bool) -> Result { - let parsed_targets = Self::parse_targets(targets)?; - - let generated_code = Self::generate_code(no_std, parsed_targets)?; + let generated_code = Self::generate_code(no_std, targets)?; let use_statements = generated_code.use_statements_for_uniquely_named_types(); @@ -68,10 +65,7 @@ impl Abigen { .expect("Wasm hotfix failed!") } - fn generate_code( - no_std: bool, - parsed_targets: Vec, - ) -> Result { + fn generate_code(no_std: bool, parsed_targets: Vec) -> Result { let custom_types = Self::filter_custom_types(&parsed_targets); let shared_types = Self::filter_shared_types(custom_types); @@ -83,7 +77,7 @@ impl Abigen { } fn generate_all_bindings( - parsed_targets: Vec, + parsed_targets: Vec, no_std: bool, shared_types: &HashSet, ) -> Result { @@ -96,7 +90,7 @@ impl Abigen { } fn generate_binding( - target: ParsedAbigenTarget, + target: AbigenTarget, no_std: bool, shared_types: &HashSet, ) -> Result { @@ -128,13 +122,6 @@ impl Abigen { GeneratedCode::new(code, Default::default(), no_std) } - fn parse_targets(targets: Vec) -> Result> { - targets - .into_iter() - .map(|target| target.try_into()) - .collect() - } - fn generate_shared_types( shared_types: HashSet, no_std: bool, @@ -150,7 +137,7 @@ impl Abigen { } fn filter_custom_types( - all_types: &[ParsedAbigenTarget], + all_types: &[AbigenTarget], ) -> impl Iterator { all_types .iter() diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs index a4e26e27c..caeed6631 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs @@ -3,55 +3,19 @@ use std::{convert::TryFrom, path::PathBuf, str::FromStr}; use fuel_abi_types::abi::full_program::FullProgramABI; use proc_macro2::Ident; -use crate::{ - error, - error::{Error, Result}, - utils::Source, -}; +use crate::{error, error::Error}; #[derive(Debug, Clone)] pub struct AbigenTarget { - pub name: String, - pub abi: String, - pub program_type: ProgramType, -} - -pub(crate) struct Abi { - pub(crate) path: Option, - pub(crate) abi: FullProgramABI, -} - -pub(crate) struct ParsedAbigenTarget { pub name: String, pub source: Abi, pub program_type: ProgramType, } -impl TryFrom for ParsedAbigenTarget { - type Error = Error; - - fn try_from(value: AbigenTarget) -> Result { - Ok(Self { - name: value.name, - source: parse_program_abi(&value.abi)?, - program_type: value.program_type, - }) - } -} - -fn parse_program_abi(abi_source: &str) -> Result { - let source = Source::parse(abi_source).expect("failed to parse JSON ABI"); - - let json_abi_str = source.get().expect("failed to parse JSON ABI from string"); - let abi = FullProgramABI::from_json_abi(&json_abi_str).map_err(|e| { - error!( - "{} {}", - "malformed `abi`. Did you use `forc` to create it?: ", e - ) - })?; - let path = source.path(); - - Ok(Abi { path, abi }) +#[derive(Debug, Clone)] +pub struct Abi { + pub path: Option, + pub abi: FullProgramABI, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings.rs index 68a85d697..77db3666f 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings.rs @@ -2,7 +2,7 @@ use crate::{ error::Result, program_bindings::{ abigen::{ - abigen_target::ParsedAbigenTarget, + abigen_target::AbigenTarget, bindings::{ contract::contract_bindings, predicate::predicate_bindings, script::script_bindings, }, @@ -19,7 +19,7 @@ mod predicate; mod script; mod utils; -pub(crate) fn generate_bindings(target: ParsedAbigenTarget, no_std: bool) -> Result { +pub(crate) fn generate_bindings(target: AbigenTarget, no_std: bool) -> Result { let bindings_generator = match target.program_type { ProgramType::Script => script_bindings, ProgramType::Contract => contract_bindings, diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/utils.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/utils.rs index 29f35e580..55dfc734a 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/utils.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/utils.rs @@ -16,7 +16,7 @@ pub(crate) fn extract_main_fn(abi: &[FullABIFunction]) -> Result<&FullABIFunctio .map(|candidate| candidate.name()) .collect::>(); Err(error!( - "ABI must have one and only one function with the name 'main'. Got: {fn_names:?}" + "`abi` must have only one function with the name 'main'. Got: {fn_names:?}" )) } } @@ -45,7 +45,7 @@ mod tests { assert_eq!( err.to_string(), - r#"ABI must have one and only one function with the name 'main'. Got: ["main", "another", "main"]"# + r#"`abi` must have only one function with the name 'main'. Got: ["main", "another", "main"]"# ); } diff --git a/packages/fuels-code-gen/src/utils.rs b/packages/fuels-code-gen/src/utils.rs index 7a405c360..fd2081360 100644 --- a/packages/fuels-code-gen/src/utils.rs +++ b/packages/fuels-code-gen/src/utils.rs @@ -2,3 +2,17 @@ pub use fuel_abi_types::utils::{ident, safe_ident, TypePath}; pub use source::Source; mod source; + +use crate::{error, error::Result, Abi}; +use fuel_abi_types::abi::full_program::FullProgramABI; + +pub fn parse_program_abi(abi: &str) -> Result { + let source = Source::parse(abi)?; + + let json_abi_str = source.get()?; + let abi = FullProgramABI::from_json_abi(&json_abi_str) + .map_err(|e| error!("malformed `abi`. Did you use `forc` to create it?: ").combine(e))?; + let path = source.path(); + + Ok(Abi { path, abi }) +} diff --git a/packages/fuels-code-gen/src/utils/source.rs b/packages/fuels-code-gen/src/utils/source.rs index 63d479ef2..67fde36f6 100644 --- a/packages/fuels-code-gen/src/utils/source.rs +++ b/packages/fuels-code-gen/src/utils/source.rs @@ -39,7 +39,18 @@ impl Source { if source.starts_with('{') || source.starts_with('[') || source.starts_with('\n') { return Ok(Source::String(source.to_owned())); } - let root = env::current_dir()?.canonicalize()?; + + let current_dir = env::current_dir() + .map_err(|e| error!("unable to get current directory: ").combine(e))?; + + let root = current_dir.canonicalize().map_err(|e| { + error!( + "unable to canonicalize current directory {}: ", + current_dir.display() + ) + .combine(e) + })?; + Ok(Source::with_root(root, source)) } @@ -62,12 +73,11 @@ impl Source { Source::Local(path.as_ref().into()) } - /// Retrieves the source JSON of the artifact this will either read the JSON - /// from the file system or retrieve a contract ABI from the network - /// depending on the source type. + /// Retrieves the source JSON of the artifact. Either read the JSON + /// from the file system or the provided `String`. pub fn get(&self) -> Result { match self { - Source::Local(path) => get_local_contract(path), + Source::Local(path) => read_abi(path), Source::String(abi) => Ok(abi.clone()), } } @@ -80,17 +90,17 @@ impl Source { } } -fn get_local_contract(path: &Path) -> Result { +fn read_abi(path: &Path) -> Result { let path = if path.is_relative() { let absolute_path = path.canonicalize().map_err(|e| { error!( - "unable to canonicalize file from working dir {} with path {}", + "unable to canonicalize file from working dir {} with path {}: {}", env::current_dir() .map(|cwd| cwd.display().to_string()) .unwrap_or_else(|err| format!("??? ({err})")), - path.display() + path.display(), + e ) - .combine(e) })?; Cow::Owned(absolute_path) } else { @@ -99,11 +109,12 @@ fn get_local_contract(path: &Path) -> Result { let json = fs::read_to_string(&path).map_err(|e| { error!( - "failed to read artifact JSON file with path {}", - path.display() + "failed to read `abi` file with path {}: {}", + path.display(), + e ) - .combine(e) })?; + Ok(json) } From 80c800daf5d7dae8e5c4f6ee323882dff940e51b Mon Sep 17 00:00:00 2001 From: hal3e Date: Fri, 26 Apr 2024 11:44:40 +0200 Subject: [PATCH 4/7] fmt --- packages/fuels-code-gen/src/utils.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/fuels-code-gen/src/utils.rs b/packages/fuels-code-gen/src/utils.rs index fd2081360..d6c2ced46 100644 --- a/packages/fuels-code-gen/src/utils.rs +++ b/packages/fuels-code-gen/src/utils.rs @@ -3,9 +3,10 @@ pub use source::Source; mod source; -use crate::{error, error::Result, Abi}; use fuel_abi_types::abi::full_program::FullProgramABI; +use crate::{error, error::Result, Abi}; + pub fn parse_program_abi(abi: &str) -> Result { let source = Source::parse(abi)?; From 73be0c4c0492353a28b227d459b02717f15e209c Mon Sep 17 00:00:00 2001 From: hal3e Date: Thu, 2 May 2024 10:43:06 +0200 Subject: [PATCH 5/7] pr comments --- .../program_bindings/abigen/abigen_target.rs | 59 +++++++++++++++++-- packages/fuels-code-gen/src/utils.rs | 15 ----- packages/fuels-macros/src/abigen/parsing.rs | 14 ++--- .../src/setup_program_test/code_gen.rs | 15 ++--- 4 files changed, 66 insertions(+), 37 deletions(-) diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs index caeed6631..8c3ec29bf 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs @@ -3,19 +3,66 @@ use std::{convert::TryFrom, path::PathBuf, str::FromStr}; use fuel_abi_types::abi::full_program::FullProgramABI; use proc_macro2::Ident; -use crate::{error, error::Error}; +use crate::{ + error::{error, Error, Result}, + utils::Source, +}; #[derive(Debug, Clone)] pub struct AbigenTarget { - pub name: String, - pub source: Abi, - pub program_type: ProgramType, + pub(crate) name: String, + pub(crate) source: Abi, + pub(crate) program_type: ProgramType, +} + +impl AbigenTarget { + pub fn new(name: String, source: Abi, program_type: ProgramType) -> Self { + Self { + name, + source, + program_type, + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn source(&self) -> &Abi { + &self.source + } + + pub fn program_type(&self) -> ProgramType { + self.program_type + } } #[derive(Debug, Clone)] pub struct Abi { - pub path: Option, - pub abi: FullProgramABI, + pub(crate) path: Option, + pub(crate) abi: FullProgramABI, +} + +impl Abi { + pub fn parse(abi: &str) -> Result { + let source = Source::parse(abi)?; + + let json_abi_str = source.get()?; + let abi = FullProgramABI::from_json_abi(&json_abi_str).map_err(|e| { + error!("malformed `abi`. Did you use `forc` to create it?: ").combine(e) + })?; + let path = source.path(); + + Ok(Abi { path, abi }) + } + + pub fn path(&self) -> Option<&PathBuf> { + self.path.as_ref() + } + + pub fn abi(&self) -> &FullProgramABI { + &self.abi + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/packages/fuels-code-gen/src/utils.rs b/packages/fuels-code-gen/src/utils.rs index d6c2ced46..7a405c360 100644 --- a/packages/fuels-code-gen/src/utils.rs +++ b/packages/fuels-code-gen/src/utils.rs @@ -2,18 +2,3 @@ pub use fuel_abi_types::utils::{ident, safe_ident, TypePath}; pub use source::Source; mod source; - -use fuel_abi_types::abi::full_program::FullProgramABI; - -use crate::{error, error::Result, Abi}; - -pub fn parse_program_abi(abi: &str) -> Result { - let source = Source::parse(abi)?; - - let json_abi_str = source.get()?; - let abi = FullProgramABI::from_json_abi(&json_abi_str) - .map_err(|e| error!("malformed `abi`. Did you use `forc` to create it?: ").combine(e))?; - let path = source.path(); - - Ok(Abi { path, abi }) -} diff --git a/packages/fuels-macros/src/abigen/parsing.rs b/packages/fuels-macros/src/abigen/parsing.rs index 50ef012a4..38b145dcc 100644 --- a/packages/fuels-macros/src/abigen/parsing.rs +++ b/packages/fuels-macros/src/abigen/parsing.rs @@ -1,4 +1,4 @@ -use fuels_code_gen::{utils::parse_program_abi, Abi, AbigenTarget, ProgramType}; +use fuels_code_gen::{Abi, AbigenTarget, ProgramType}; use syn::{ parse::{Parse, ParseStream}, Result, @@ -14,11 +14,11 @@ impl From for Vec { impl From for AbigenTarget { fn from(macro_target: MacroAbigenTarget) -> Self { - AbigenTarget { - name: macro_target.name, - source: macro_target.source, - program_type: macro_target.program_type, - } + AbigenTarget::new( + macro_target.name, + macro_target.source, + macro_target.program_type, + ) } } @@ -56,7 +56,7 @@ impl MacroAbigenTarget { let name = name_values.get_as_lit_str("name")?.value(); let abi_lit_str = name_values.get_as_lit_str("abi")?; - let source = parse_program_abi(&abi_lit_str.value()) + let source = Abi::parse(&abi_lit_str.value()) .map_err(|e| syn::Error::new(abi_lit_str.span(), e.to_string()))?; Ok(Self { diff --git a/packages/fuels-macros/src/setup_program_test/code_gen.rs b/packages/fuels-macros/src/setup_program_test/code_gen.rs index 63fe1b1b1..a6ab6a19d 100644 --- a/packages/fuels-macros/src/setup_program_test/code_gen.rs +++ b/packages/fuels-macros/src/setup_program_test/code_gen.rs @@ -3,10 +3,7 @@ use std::{ path::{Path, PathBuf}, }; -use fuels_code_gen::{ - utils::{ident, parse_program_abi}, - Abigen, AbigenTarget, ProgramType, -}; +use fuels_code_gen::{utils::ident, Abi, Abigen, AbigenTarget, ProgramType}; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::LitStr; @@ -65,14 +62,14 @@ fn parse_abigen_targets( project_lookup .iter() .map(|(name, project)| { - let source = parse_program_abi(&project.abi_path()) + let source = Abi::parse(&project.abi_path()) .map_err(|e| syn::Error::new(project.path_span, e.to_string()))?; - Ok(AbigenTarget { - name: name.clone(), + Ok(AbigenTarget::new( + name.clone(), source, - program_type: project.program_type, - }) + project.program_type, + )) }) .collect() } From 8ba0cd8949cf9c4a5d9ed6fd94a65160dd0f1800 Mon Sep 17 00:00:00 2001 From: hal3e Date: Thu, 2 May 2024 19:02:37 +0200 Subject: [PATCH 6/7] pr comments --- .../program_bindings/abigen/abigen_target.rs | 77 +++++++++-- packages/fuels-code-gen/src/utils.rs | 3 - packages/fuels-code-gen/src/utils/source.rs | 127 ------------------ packages/fuels-macros/src/abigen/parsing.rs | 18 ++- .../src/setup_program_test/code_gen.rs | 2 +- 5 files changed, 80 insertions(+), 147 deletions(-) delete mode 100644 packages/fuels-code-gen/src/utils/source.rs diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs index 8c3ec29bf..fbd5e15eb 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs @@ -1,12 +1,14 @@ -use std::{convert::TryFrom, path::PathBuf, str::FromStr}; +use std::{ + convert::TryFrom, + env, fs, + path::{Path, PathBuf}, + str::FromStr, +}; use fuel_abi_types::abi::full_program::FullProgramABI; use proc_macro2::Ident; -use crate::{ - error::{error, Error, Result}, - utils::Source, -}; +use crate::error::{error, Error, Result}; #[derive(Debug, Clone)] pub struct AbigenTarget { @@ -44,16 +46,57 @@ pub struct Abi { } impl Abi { - pub fn parse(abi: &str) -> Result { - let source = Source::parse(abi)?; + pub fn load_from(path: impl AsRef) -> Result { + let path = Self::canonicalize_path(path.as_ref())?; + + let json_abi = fs::read_to_string(&path).map_err(|e| { + error!( + "failed to read `abi` file with path {}: {}", + path.display(), + e + ) + })?; + let abi = Self::parse_from_json(&json_abi)?; + + Ok(Abi { + path: Some(path), + abi, + }) + } + + fn canonicalize_path(path: &Path) -> Result { + let current_dir = env::current_dir() + .map_err(|e| error!("unable to get current directory: ").combine(e))?; - let json_abi_str = source.get()?; - let abi = FullProgramABI::from_json_abi(&json_abi_str).map_err(|e| { - error!("malformed `abi`. Did you use `forc` to create it?: ").combine(e) + let root = current_dir.canonicalize().map_err(|e| { + error!( + "unable to canonicalize current directory {}: ", + current_dir.display() + ) + .combine(e) })?; - let path = source.path(); - Ok(Abi { path, abi }) + let path = root.join(path); + + if path.is_relative() { + path.canonicalize().map_err(|e| { + error!( + "unable to canonicalize file from working dir {} with path {}: {}", + env::current_dir() + .map(|cwd| cwd.display().to_string()) + .unwrap_or_else(|err| format!("??? ({err})")), + path.display(), + e + ) + }) + } else { + Ok(path) + } + } + + fn parse_from_json(json_abi: &str) -> Result { + FullProgramABI::from_json_abi(json_abi) + .map_err(|e| error!("malformed `abi`. Did you use `forc` to create it?: ").combine(e)) } pub fn path(&self) -> Option<&PathBuf> { @@ -65,6 +108,16 @@ impl Abi { } } +impl FromStr for Abi { + type Err = Error; + + fn from_str(json_abi: &str) -> Result { + let abi = Abi::parse_from_json(json_abi)?; + + Ok(Abi { path: None, abi }) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProgramType { Script, diff --git a/packages/fuels-code-gen/src/utils.rs b/packages/fuels-code-gen/src/utils.rs index 7a405c360..d0e1259ff 100644 --- a/packages/fuels-code-gen/src/utils.rs +++ b/packages/fuels-code-gen/src/utils.rs @@ -1,4 +1 @@ pub use fuel_abi_types::utils::{ident, safe_ident, TypePath}; -pub use source::Source; - -mod source; diff --git a/packages/fuels-code-gen/src/utils/source.rs b/packages/fuels-code-gen/src/utils/source.rs deleted file mode 100644 index 67fde36f6..000000000 --- a/packages/fuels-code-gen/src/utils/source.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::{ - borrow::Cow, - env, fs, - path::{Path, PathBuf}, - str::FromStr, -}; - -use crate::error::{error, Error, Result}; - -/// A source of a Truffle artifact JSON. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Source { - /// A raw ABI string - String(String), - - /// An ABI located on the local file system. - Local(PathBuf), -} - -impl Source { - /// Parses an ABI from a source - /// - /// Contract ABIs can be retrieved from the local filesystem or it can - /// be provided in-line. It accepts: - /// - /// - raw ABI JSON - /// - /// - `relative/path/to/Contract.json`: a relative path to an ABI JSON file. - /// This relative path is rooted in the current working directory. - /// To specify the root for relative paths, use `Source::with_root`. - /// - /// - `/absolute/path/to/Contract.json to an ABI JSON file. - pub fn parse(source: S) -> Result - where - S: AsRef, - { - let source = source.as_ref().trim(); - - if source.starts_with('{') || source.starts_with('[') || source.starts_with('\n') { - return Ok(Source::String(source.to_owned())); - } - - let current_dir = env::current_dir() - .map_err(|e| error!("unable to get current directory: ").combine(e))?; - - let root = current_dir.canonicalize().map_err(|e| { - error!( - "unable to canonicalize current directory {}: ", - current_dir.display() - ) - .combine(e) - })?; - - Ok(Source::with_root(root, source)) - } - - /// Parses an artifact source from a string and a specified root directory - /// for resolving relative paths. See `Source::with_root` for more details - /// on supported source strings. - fn with_root(root: P, source: S) -> Self - where - P: AsRef, - S: AsRef, - { - Source::local(root.as_ref().join(source.as_ref())) - } - - /// Creates a local filesystem source from a path string. - fn local

(path: P) -> Self - where - P: AsRef, - { - Source::Local(path.as_ref().into()) - } - - /// Retrieves the source JSON of the artifact. Either read the JSON - /// from the file system or the provided `String`. - pub fn get(&self) -> Result { - match self { - Source::Local(path) => read_abi(path), - Source::String(abi) => Ok(abi.clone()), - } - } - - pub fn path(&self) -> Option { - match self { - Source::Local(path) => Some(path.clone()), - _ => None, - } - } -} - -fn read_abi(path: &Path) -> Result { - let path = if path.is_relative() { - let absolute_path = path.canonicalize().map_err(|e| { - error!( - "unable to canonicalize file from working dir {} with path {}: {}", - env::current_dir() - .map(|cwd| cwd.display().to_string()) - .unwrap_or_else(|err| format!("??? ({err})")), - path.display(), - e - ) - })?; - Cow::Owned(absolute_path) - } else { - Cow::Borrowed(path) - }; - - let json = fs::read_to_string(&path).map_err(|e| { - error!( - "failed to read `abi` file with path {}: {}", - path.display(), - e - ) - })?; - - Ok(json) -} - -impl FromStr for Source { - type Err = Error; - - fn from_str(s: &str) -> Result { - Source::parse(s) - } -} diff --git a/packages/fuels-macros/src/abigen/parsing.rs b/packages/fuels-macros/src/abigen/parsing.rs index 38b145dcc..b2e38331b 100644 --- a/packages/fuels-macros/src/abigen/parsing.rs +++ b/packages/fuels-macros/src/abigen/parsing.rs @@ -1,7 +1,7 @@ use fuels_code_gen::{Abi, AbigenTarget, ProgramType}; use syn::{ parse::{Parse, ParseStream}, - Result, + LitStr, Result, }; use crate::parse_utils::{Command, UniqueNameValues}; @@ -55,9 +55,7 @@ impl MacroAbigenTarget { let name = name_values.get_as_lit_str("name")?.value(); let abi_lit_str = name_values.get_as_lit_str("abi")?; - - let source = Abi::parse(&abi_lit_str.value()) - .map_err(|e| syn::Error::new(abi_lit_str.span(), e.to_string()))?; + let source = Self::parse_inline_or_load_abi(abi_lit_str)?; Ok(Self { name, @@ -65,4 +63,16 @@ impl MacroAbigenTarget { program_type, }) } + + fn parse_inline_or_load_abi(abi_lit_str: &LitStr) -> Result { + let abi_string = abi_lit_str.value(); + let abi_str = abi_string.trim(); + + if abi_str.starts_with('{') || abi_str.starts_with('[') || abi_str.starts_with('\n') { + abi_str.parse() + } else { + Abi::load_from(abi_str) + } + .map_err(|e| syn::Error::new(abi_lit_str.span(), e.to_string())) + } } diff --git a/packages/fuels-macros/src/setup_program_test/code_gen.rs b/packages/fuels-macros/src/setup_program_test/code_gen.rs index a6ab6a19d..2c3d68444 100644 --- a/packages/fuels-macros/src/setup_program_test/code_gen.rs +++ b/packages/fuels-macros/src/setup_program_test/code_gen.rs @@ -62,7 +62,7 @@ fn parse_abigen_targets( project_lookup .iter() .map(|(name, project)| { - let source = Abi::parse(&project.abi_path()) + let source = Abi::load_from(project.abi_path()) .map_err(|e| syn::Error::new(project.path_span, e.to_string()))?; Ok(AbigenTarget::new( From 06730f7a1cefd2b218242683ad78746d05fb2976 Mon Sep 17 00:00:00 2001 From: hal3e Date: Fri, 3 May 2024 12:27:28 +0200 Subject: [PATCH 7/7] pr comments --- packages/fuels-code-gen/src/program_bindings/abigen.rs | 4 ++-- .../tests/ui/setup_program_test/invalid_project_path.stderr | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/fuels-code-gen/src/program_bindings/abigen.rs b/packages/fuels-code-gen/src/program_bindings/abigen.rs index 304cf2910..6d3269749 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen.rs @@ -77,11 +77,11 @@ impl Abigen { } fn generate_all_bindings( - parsed_targets: Vec, + targets: Vec, no_std: bool, shared_types: &HashSet, ) -> Result { - parsed_targets + targets .into_iter() .map(|target| Self::generate_binding(target, no_std, shared_types)) .fold_ok(GeneratedCode::default(), |acc, generated_code| { diff --git a/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.stderr b/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.stderr index 27bde8e8a..b13c26030 100644 --- a/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.stderr +++ b/packages/fuels-macros/tests/ui/setup_program_test/invalid_project_path.stderr @@ -1,4 +1,4 @@ -error: failed to read `abi` file with path $WORKSPACE/target/tests/trybuild/fuels-macros/out/debug/fuels-macros-abi.json: No such file or directory (os error 2) +error: failed to read `abi` file with path $WORKSPACE/target/tests/trybuild/fuels-macros/out/release/fuels-macros-abi.json: No such file or directory (os error 2) --> tests/ui/setup_program_test/invalid_project_path.rs:3:68 | 3 | setup_program_test!(Abigen(Contract(name = "MyContract", project = ".")));