diff --git a/docs/src/types/custom_types.md b/docs/src/types/custom_types.md index 5fe79d9bd4..c151db9f0c 100644 --- a/docs/src/types/custom_types.md +++ b/docs/src/types/custom_types.md @@ -59,3 +59,45 @@ Your Rust code would look like this: ```rust,ignore {{#include ../../../packages/fuels/tests/types_contracts.rs:generic}} ``` + +### Unused generic type parameters + +Sway supports unused generic type parameters when declaring structs/enums: + +```Rust +struct SomeStruct { + field: u64 +} + +enum SomeEnum { + One: u64 +} + +``` + +If you tried the same in Rust you'd get complaints that `T` and `K` must be used or removed. When generating Rust bindings for such types we make use of the [`PhantomData`](https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters) type. The generated bindings for the above example would look something like this: + +```Rust +struct SomeStruct { + pub field: u64, + pub _unused_generic_0: PhantomData + pub _unused_generic_1: PhantomData +} + +enum SomeEnum { + One(u64), + IgnoreMe(PhantomData, PhantomData) +} +``` + +To lessen the impact to developer experience you may use `SomeStruct::new` to initialize the above structure without bothering with the `PhantomData`s: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:unused_generics_struct}} +``` + +If your struct doesn't have any fields we'll also derive `Default`. As for enums all `PhantomData`s are placed inside a new variant called `IgnoreMe` which you'll need to ignore in your matches: + +```rust,ignore +{{#include ../../../examples/types/src/lib.rs:unused_generics_enum}} +``` diff --git a/examples/types/src/lib.rs b/examples/types/src/lib.rs index 45314fc017..b418546cac 100644 --- a/examples/types/src/lib.rs +++ b/examples/types/src/lib.rs @@ -166,4 +166,157 @@ mod tests { // ANCHOR_END: type_conversion Ok(()) } + + #[tokio::test] + async fn unused_generics() -> Result<()> { + use fuels::prelude::*; + abigen!(Contract( + name = "MyContract", + abi = r#" { + "types": [ + { + "typeId": 0, + "type": "()", + "components": [], + "typeParameters": null + }, + { + "typeId": 1, + "type": "enum MyEnum", + "components": [ + { + "name": "One", + "type": 7, + "typeArguments": null + } + ], + "typeParameters": [ + 3, + 2 + ] + }, + { + "typeId": 2, + "type": "generic K", + "components": null, + "typeParameters": null + }, + { + "typeId": 3, + "type": "generic T", + "components": null, + "typeParameters": null + }, + { + "typeId": 4, + "type": "struct MyStruct", + "components": [ + { + "name": "field", + "type": 7, + "typeArguments": null + } + ], + "typeParameters": [ + 3, + 2 + ] + }, + { + "typeId": 5, + "type": "u16", + "components": null, + "typeParameters": null + }, + { + "typeId": 6, + "type": "u32", + "components": null, + "typeParameters": null + }, + { + "typeId": 7, + "type": "u64", + "components": null, + "typeParameters": null + }, + { + "typeId": 8, + "type": "u8", + "components": null, + "typeParameters": null + } + ], + "functions": [ + { + "inputs": [ + { + "name": "arg", + "type": 4, + "typeArguments": [ + { + "name": "", + "type": 8, + "typeArguments": null + }, + { + "name": "", + "type": 5, + "typeArguments": null + } + ] + }, + { + "name": "arg_2", + "type": 1, + "typeArguments": [ + { + "name": "", + "type": 6, + "typeArguments": null + }, + { + "name": "", + "type": 7, + "typeArguments": null + } + ] + } + ], + "name": "test_function", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + } + ], + "loggedTypes": [], + "messagesTypes": [], + "configurables": [] +}"# + )); + + // ANCHOR: unused_generics_struct + assert_eq!( + >::new(15), + MyStruct { + field: 15, + _unused_generic_0: std::marker::PhantomData, + _unused_generic_1: std::marker::PhantomData + } + ); + // ANCHOR_END: unused_generics_struct + + let my_enum = >::One(15); + // ANCHOR: unused_generics_enum + match my_enum { + MyEnum::One(_value) => {} + MyEnum::IgnoreMe(..) => panic!("Will never receive this variant"), + } + // ANCHOR_END: unused_generics_enum + + Ok(()) + } } diff --git a/packages/fuels-code-gen/Cargo.toml b/packages/fuels-code-gen/Cargo.toml index b6c1e609dd..111f209f91 100644 --- a/packages/fuels-code-gen/Cargo.toml +++ b/packages/fuels-code-gen/Cargo.toml @@ -19,5 +19,9 @@ regex = { workspace = true } serde_json = { workspace = true } syn = { workspace = true } +[dev-dependencies] +pretty_assertions = "1.4" + + [package.metadata.cargo-machete] ignored = ["Inflector"] diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/contract.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/contract.rs index 91ab078d92..38432feda6 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/contract.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/contract.rs @@ -162,16 +162,21 @@ pub(crate) fn expand_fn(abi_fun: &FullABIFunction) -> Result { }; generator.set_body(body); - Ok(generator.into()) + Ok(generator.generate()) } #[cfg(test)] mod tests { use std::collections::HashMap; - use fuel_abi_types::abi::program::{ABIFunction, ProgramABI, TypeApplication, TypeDeclaration}; + use fuel_abi_types::abi::{ + full_program::FullABIFunction, + program::{ABIFunction, ProgramABI, TypeApplication, TypeDeclaration}, + }; + use pretty_assertions::assert_eq; + use quote::quote; - use super::*; + use crate::{error::Result, program_bindings::abigen::bindings::contract::expand_fn}; #[test] fn test_expand_fn_simple_abi() -> Result<()> { @@ -395,13 +400,13 @@ mod tests { let expected = quote! { #[doc = "Calls the contract's `HelloWorld` function"] - pub fn HelloWorld(&self, bimbam: bool) -> ::fuels::programs::contract::ContractCallHandler { + pub fn HelloWorld(&self, bimbam: ::core::primitive::bool) -> ::fuels::programs::contract::ContractCallHandler { ::fuels::programs::contract::method_hash( self.contract_id.clone(), self.account.clone(), ::fuels::core::codec::resolve_fn_selector( "HelloWorld", - &[::param_type()] + &[<::core::primitive::bool as ::fuels::core::traits::Parameterize>::param_type()] ), &[::fuels::core::traits::Tokenizable::into_token(bimbam)], self.log_decoder.clone(), diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs index 9304a9ffa2..ec3480a81e 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs @@ -1,4 +1,4 @@ -use fuel_abi_types::abi::full_program::{FullABIFunction, FullTypeApplication}; +use fuel_abi_types::abi::full_program::FullABIFunction; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; @@ -6,7 +6,7 @@ use crate::{ error::Result, program_bindings::{ resolved_type::TypeResolver, - utils::{get_equivalent_bech32_type, param_type_calls, Component}, + utils::{get_equivalent_bech32_type, Components}, }, utils::{safe_ident, TypePath}, }; @@ -14,7 +14,7 @@ use crate::{ #[derive(Debug)] pub(crate) struct FunctionGenerator { name: String, - args: Vec, + args: Components, output_type: TokenStream, body: TokenStream, doc: Option, @@ -23,7 +23,11 @@ pub(crate) struct FunctionGenerator { impl FunctionGenerator { pub fn new(fun: &FullABIFunction) -> Result { - let args = function_arguments(fun.inputs())?; + // All abi-method-calling Rust functions are currently generated at the top-level-mod of + // the Program in question (e.g. abigen_bindings::my_contract_mod`). If we ever nest + // these functions in a deeper mod we would need to propagate the mod to here instead of + // just hard-coding the default path. + let args = Components::new(fun.inputs(), true, TypePath::default())?; // We are not checking that the ABI contains non-SDK supported types so that the user can // still interact with an ABI even if some methods will fail at runtime. @@ -59,22 +63,19 @@ impl FunctionGenerator { } pub fn fn_selector(&self) -> TokenStream { - let param_type_calls = param_type_calls(&self.args); + let param_type_calls = self.args.param_type_calls(); let name = &self.name; quote! {::fuels::core::codec::resolve_fn_selector(#name, &[#(#param_type_calls),*])} } pub fn tokenized_args(&self) -> TokenStream { - let arg_names = self.args.iter().map(|component| { - let field_name = &component.field_name; - let field_type = &component.field_type; - - get_equivalent_bech32_type(&field_type.type_name.to_string()) + let arg_names = self.args.iter().map(|(name, ty)| { + get_equivalent_bech32_type(ty) .map(|_| { - quote! {#field_type::from(#field_name.into())} + quote! {<#ty>::from(#name.into())} }) - .unwrap_or(quote! {#field_name}) + .unwrap_or(quote! {#name}) }); quote! {[#(::fuels::core::traits::Tokenizable::into_token(#arg_names)),*]} } @@ -87,26 +88,10 @@ impl FunctionGenerator { pub fn output_type(&self) -> &TokenStream { &self.output_type } -} - -fn function_arguments(inputs: &[FullTypeApplication]) -> Result> { - inputs - .iter() - .map(|input| { - // All abi-method-calling Rust functions are currently generated at the top-level-mod of - // the Program in question (e.g. abigen_bindings::my_contract_mod`). If we ever nest - // these functions in a deeper mod we would need to propagate the mod to here instead of - // just hard-coding the default path. - let mod_of_component = TypePath::default(); - Component::new(input, true, mod_of_component) - }) - .collect::>() -} -impl From<&FunctionGenerator> for TokenStream { - fn from(fun: &FunctionGenerator) -> Self { - let name = safe_ident(&fun.name); - let doc = fun + pub fn generate(&self) -> TokenStream { + let name = safe_ident(&self.name); + let doc = self .doc .as_ref() .map(|text| { @@ -114,21 +99,18 @@ impl From<&FunctionGenerator> for TokenStream { }) .unwrap_or_default(); - let arg_declarations = fun.args.iter().map(|component| { - let name = &component.field_name; - let field_type = &component.field_type; - - get_equivalent_bech32_type(&field_type.type_name.to_string()) + let arg_declarations = self.args.iter().map(|(name, ty)| { + get_equivalent_bech32_type(ty) .map(|new_type| { quote! { #name: impl ::core::convert::Into<#new_type> } }) - .unwrap_or(quote! { #name: #field_type }) + .unwrap_or(quote! { #name: #ty }) }); - let output_type = fun.output_type(); - let body = &fun.body; + let output_type = self.output_type(); + let body = &self.body; - let self_param = fun.is_method.then_some(quote! {&self,}); + let self_param = self.is_method.then_some(quote! {&self,}); let params = quote! { #self_param #(#arg_declarations),* }; @@ -141,186 +123,13 @@ impl From<&FunctionGenerator> for TokenStream { } } -impl From for TokenStream { - fn from(fun: FunctionGenerator) -> Self { - (&fun).into() - } -} - #[cfg(test)] mod tests { - use std::collections::HashMap; - - use fuel_abi_types::abi::{ - full_program::FullTypeDeclaration, - program::{ABIFunction, TypeApplication, TypeDeclaration}, - }; + use fuel_abi_types::abi::full_program::{FullTypeApplication, FullTypeDeclaration}; + use pretty_assertions::assert_eq; use super::*; - #[test] - fn test_expand_fn_arguments() -> Result<()> { - let the_argument = TypeApplication { - name: "some_argument".to_string(), - type_id: 0, - ..Default::default() - }; - - // All arguments are here - let the_function = ABIFunction { - inputs: vec![the_argument], - name: "some_fun".to_string(), - ..ABIFunction::default() - }; - - let types = [( - 0, - TypeDeclaration { - type_id: 0, - type_field: String::from("u32"), - ..Default::default() - }, - )] - .into_iter() - .collect::>(); - let result = - function_arguments(FullABIFunction::from_counterpart(&the_function, &types)?.inputs())?; - let component = &result[0]; - - assert_eq!(&component.field_name.to_string(), "some_argument"); - assert_eq!(&component.field_type.to_string(), "u32"); - - Ok(()) - } - - #[test] - fn test_expand_fn_arguments_primitive() -> Result<()> { - let the_function = ABIFunction { - inputs: vec![TypeApplication { - name: "bim_bam".to_string(), - type_id: 1, - ..Default::default() - }], - name: "pip_pop".to_string(), - ..Default::default() - }; - - let types = [ - ( - 0, - TypeDeclaration { - type_id: 0, - type_field: String::from("()"), - ..Default::default() - }, - ), - ( - 1, - TypeDeclaration { - type_id: 1, - type_field: String::from("u64"), - ..Default::default() - }, - ), - ] - .into_iter() - .collect::>(); - let result = - function_arguments(FullABIFunction::from_counterpart(&the_function, &types)?.inputs())?; - let component = &result[0]; - - assert_eq!(&component.field_name.to_string(), "bim_bam"); - assert_eq!(&component.field_type.to_string(), "u64"); - - Ok(()) - } - - #[test] - fn test_expand_fn_arguments_composite() -> Result<()> { - let mut function = ABIFunction { - inputs: vec![TypeApplication { - name: "bim_bam".to_string(), - type_id: 0, - ..Default::default() - }], - name: "PipPopFunction".to_string(), - ..Default::default() - }; - - let types = [ - ( - 0, - TypeDeclaration { - type_id: 0, - type_field: "struct CarMaker".to_string(), - components: Some(vec![TypeApplication { - name: "name".to_string(), - type_id: 1, - ..Default::default() - }]), - ..Default::default() - }, - ), - ( - 1, - TypeDeclaration { - type_id: 1, - type_field: "str[5]".to_string(), - ..Default::default() - }, - ), - ( - 2, - TypeDeclaration { - type_id: 2, - type_field: "enum Cocktail".to_string(), - components: Some(vec![TypeApplication { - name: "variant".to_string(), - type_id: 3, - ..Default::default() - }]), - ..Default::default() - }, - ), - ( - 3, - TypeDeclaration { - type_id: 3, - type_field: "u32".to_string(), - ..Default::default() - }, - ), - ] - .into_iter() - .collect::>(); - let result = - function_arguments(FullABIFunction::from_counterpart(&function, &types)?.inputs())?; - - assert_eq!(&result[0].field_name.to_string(), "bim_bam"); - assert_eq!(&result[0].field_type.to_string(), "self :: CarMaker"); - - function.inputs[0].type_id = 2; - let result = - function_arguments(FullABIFunction::from_counterpart(&function, &types)?.inputs())?; - - assert_eq!(&result[0].field_name.to_string(), "bim_bam"); - assert_eq!(&result[0].field_type.to_string(), "self :: Cocktail"); - - Ok(()) - } - - #[test] - fn correct_output_type() -> Result<()> { - let function = given_a_fun(); - let sut = FunctionGenerator::new(&function)?; - - let output_type = sut.output_type(); - - assert_eq!(output_type.to_string(), "self :: CustomStruct < u64 >"); - - Ok(()) - } - #[test] fn correct_fn_selector_resolving_code() -> Result<()> { let function = given_a_fun(); @@ -328,10 +137,12 @@ mod tests { let fn_selector_code = sut.fn_selector(); - assert_eq!( - fn_selector_code.to_string(), - r#":: fuels :: core :: codec :: resolve_fn_selector ("test_function" , & [< self :: CustomStruct :: < u8 > as :: fuels :: core :: traits :: Parameterize > :: param_type ()])"# - ); + let expected = quote! { + ::fuels::core::codec::resolve_fn_selector( + "test_function", + &[ as::fuels::core::traits::Parameterize>::param_type()]) + }; + assert_eq!(fn_selector_code.to_string(), expected.to_string()); Ok(()) } @@ -361,12 +172,12 @@ mod tests { .set_body(quote! {this is ze body}); // when - let tokenized: TokenStream = sut.into(); + let tokenized: TokenStream = sut.generate(); // then let expected = quote! { #[doc = "This is a doc"] - pub fn test_function(&self, arg_0: self::CustomStruct) -> self::CustomStruct { + pub fn test_function(&self, arg_0: self::CustomStruct<::core::primitive::u8>) -> self::CustomStruct<::core::primitive::u64> { this is ze body } }; diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/predicate.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/predicate.rs index 57a50452ed..cdb994059d 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/predicate.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/predicate.rs @@ -65,5 +65,5 @@ fn expand_fn(abi: &FullProgramABI) -> Result { .make_fn_associated() .set_body(body); - Ok(generator.into()) + Ok(generator.generate()) } diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs index c0739d2ca8..be597f95ac 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/script.rs @@ -113,5 +113,5 @@ fn expand_fn(abi: &FullProgramABI) -> Result { .set_doc("Run the script's `main` function with the provided arguments".to_string()) .set_body(body); - Ok(generator.into()) + Ok(generator.generate()) } diff --git a/packages/fuels-code-gen/src/program_bindings/custom_types.rs b/packages/fuels-code-gen/src/program_bindings/custom_types.rs index bbd5706165..89f097d235 100644 --- a/packages/fuels-code-gen/src/program_bindings/custom_types.rs +++ b/packages/fuels-code-gen/src/program_bindings/custom_types.rs @@ -123,6 +123,7 @@ mod tests { use std::collections::HashMap; use fuel_abi_types::abi::program::{ProgramABI, TypeApplication, TypeDeclaration}; + use pretty_assertions::assert_eq; use quote::quote; use super::*; @@ -179,11 +180,11 @@ mod tests { PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] - pub enum MatchaTea<> { - LongIsland(u64), - MoscowMule(bool) + pub enum MatchaTea { + LongIsland(::core::primitive::u64), + MoscowMule(::core::primitive::bool), } }; @@ -263,11 +264,11 @@ mod tests { PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] - pub enum Amsterdam<> { + pub enum Amsterdam { Infrastructure(self::Building), - Service(u32) + Service(::core::primitive::u32), } }; @@ -325,10 +326,10 @@ mod tests { PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] - pub enum SomeEnum < > { - SomeArr([u64; 7usize]) + pub enum SomeEnum { + SomeArr([::core::primitive::u64; 7usize]), } }; @@ -399,10 +400,10 @@ mod tests { PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] - pub enum EnumLevel3<> { - El2(self::EnumLevel2) + pub enum EnumLevel3 { + El2(self::EnumLevel2), } }; @@ -474,12 +475,25 @@ mod tests { PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] - pub struct Cocktail < > { - pub long_island: bool, - pub cosmopolitan: u64, - pub mojito: u32 + pub struct Cocktail { + pub long_island: ::core::primitive::bool, + pub cosmopolitan: ::core::primitive::u64, + pub mojito: ::core::primitive::u32, + } + impl Cocktail { + pub fn new( + long_island: ::core::primitive::bool, + cosmopolitan: ::core::primitive::u64, + mojito: ::core::primitive::u32, + ) -> Self { + Self { + long_island, + cosmopolitan, + mojito, + } + } } }; @@ -507,11 +521,17 @@ mod tests { Debug, Eq, PartialEq, + ::core::default::Default, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] - pub struct SomeEmptyStruct < > {} + pub struct SomeEmptyStruct {} + impl SomeEmptyStruct { + pub fn new() -> Self { + Self {} + } + } }; assert_eq!(actual.code().to_string(), expected.to_string()); @@ -572,11 +592,19 @@ mod tests { PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] - pub struct Cocktail < > { + pub struct Cocktail { pub long_island: self::Shaker, - pub mojito: u32 + pub mojito: ::core::primitive::u32, + } + impl Cocktail { + pub fn new(long_island: self::Shaker, mojito: ::core::primitive::u32,) -> Self { + Self { + long_island, + mojito, + } + } } }; @@ -675,11 +703,16 @@ mod tests { PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] - pub struct MyStruct1 < > { - pub x: u64, - pub y: ::fuels::types::Bits256 + pub struct MyStruct1 { + pub x: ::core::primitive::u64, + pub y: ::fuels::types::Bits256, + } + impl MyStruct1 { + pub fn new(x: ::core::primitive::u64, y: ::fuels::types::Bits256,) -> Self { + Self { x, y, } + } } }; @@ -698,11 +731,16 @@ mod tests { PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] - pub struct MyStruct2 < > { - pub x: bool, - pub y: self::MyStruct1 + pub struct MyStruct2 { + pub x: ::core::primitive::bool, + pub y: self::MyStruct1, + } + impl MyStruct2 { + pub fn new(x: ::core::primitive::bool, y: self::MyStruct1,) -> Self { + Self { x, y, } + } } }; diff --git a/packages/fuels-code-gen/src/program_bindings/custom_types/enums.rs b/packages/fuels-code-gen/src/program_bindings/custom_types/enums.rs index 4c03d7a713..35c1205948 100644 --- a/packages/fuels-code-gen/src/program_bindings/custom_types/enums.rs +++ b/packages/fuels-code-gen/src/program_bindings/custom_types/enums.rs @@ -7,9 +7,9 @@ use quote::quote; use crate::{ error::{error, Result}, program_bindings::{ - custom_types::utils::{extract_components, extract_generic_parameters}, + custom_types::utils::extract_generic_parameters, generated_code::GeneratedCode, - utils::Component, + utils::{tokenize_generics, Components}, }, }; @@ -23,11 +23,11 @@ pub(crate) fn expand_custom_enum( let enum_type_path = type_decl.custom_type_path()?; let enum_ident = enum_type_path.ident().unwrap(); - let components = extract_components(type_decl, false, &enum_type_path.parent())?; + let components = Components::new(&type_decl.components, false, enum_type_path.parent())?; if components.is_empty() { return Err(error!("Enum must have at least one component!")); } - let generics = extract_generic_parameters(type_decl)?; + let generics = extract_generic_parameters(type_decl); let code = enum_decl(enum_ident, &components, &generics, no_std); @@ -38,24 +38,16 @@ pub(crate) fn expand_custom_enum( fn enum_decl( enum_ident: &Ident, - components: &[Component], - generics: &[TokenStream], + components: &Components, + generics: &[Ident], no_std: bool, ) -> TokenStream { - let enum_variants = components.iter().map( - |Component { - field_name, - field_type, - }| { - if field_type.is_unit() { - quote! {#field_name} - } else { - quote! {#field_name(#field_type)} - } - }, - ); let maybe_disable_std = no_std.then(|| quote! {#[NoStd]}); + let enum_variants = components.as_enum_variants(); + let unused_generics_variant = components.generate_variant_for_unused_generics(generics); + let (_, generics_w_bounds) = tokenize_generics(generics); + quote! { #[allow(clippy::enum_variant_names)] #[derive( @@ -65,11 +57,12 @@ fn enum_decl( PartialEq, ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] #maybe_disable_std - pub enum #enum_ident <#(#generics: ::fuels::core::traits::Tokenizable + ::fuels::core::traits::Parameterize),*> { - #(#enum_variants),* + pub enum #enum_ident #generics_w_bounds { + #(#enum_variants,)* + #unused_generics_variant } } } diff --git a/packages/fuels-code-gen/src/program_bindings/custom_types/structs.rs b/packages/fuels-code-gen/src/program_bindings/custom_types/structs.rs index 07e90960fa..7c77e9fc6f 100644 --- a/packages/fuels-code-gen/src/program_bindings/custom_types/structs.rs +++ b/packages/fuels-code-gen/src/program_bindings/custom_types/structs.rs @@ -7,9 +7,9 @@ use quote::quote; use crate::{ error::Result, program_bindings::{ - custom_types::utils::{extract_components, extract_generic_parameters}, + custom_types::utils::extract_generic_parameters, generated_code::GeneratedCode, - utils::Component, + utils::{tokenize_generics, Components}, }, }; @@ -23,8 +23,8 @@ pub(crate) fn expand_custom_struct( let struct_type_path = type_decl.custom_type_path()?; let struct_ident = struct_type_path.ident().unwrap(); - let components = extract_components(type_decl, true, &struct_type_path.parent())?; - let generic_parameters = extract_generic_parameters(type_decl)?; + let components = Components::new(&type_decl.components, true, struct_type_path.parent())?; + let generic_parameters = extract_generic_parameters(type_decl); let code = struct_decl(struct_ident, &components, &generic_parameters, no_std); @@ -35,33 +35,45 @@ pub(crate) fn expand_custom_struct( fn struct_decl( struct_ident: &Ident, - components: &[Component], - generic_parameters: &Vec, + components: &Components, + generics: &[Ident], no_std: bool, ) -> TokenStream { - let fields = components.iter().map( - |Component { - field_name, - field_type, - }| { - quote! { pub #field_name: #field_type } - }, - ); + let derive_default = components + .is_empty() + .then(|| quote!(::core::default::Default,)); + let maybe_disable_std = no_std.then(|| quote! {#[NoStd]}); + let (generics_wo_bounds, generics_w_bounds) = tokenize_generics(generics); + let (field_names, field_types): (Vec<_>, Vec<_>) = components.iter().unzip(); + let (phantom_fields, phantom_types) = + components.generate_parameters_for_unused_generics(generics); + quote! { #[derive( Clone, Debug, Eq, PartialEq, + #derive_default ::fuels::macros::Parameterize, ::fuels::macros::Tokenizable, - ::fuels::macros::TryFrom + ::fuels::macros::TryFrom, )] #maybe_disable_std - pub struct #struct_ident <#(#generic_parameters: ::fuels::core::traits::Tokenizable + ::fuels::core::traits::Parameterize, )*> { - #(#fields),* + pub struct #struct_ident #generics_w_bounds { + #( pub #field_names: #field_types, )* + #(#[Ignore] pub #phantom_fields: #phantom_types, )* + } + + impl #generics_w_bounds #struct_ident #generics_wo_bounds { + pub fn new(#(#field_names: #field_types,)*) -> Self { + Self { + #(#field_names,)* + #(#phantom_fields: ::core::default::Default::default(),)* + } + } } } } diff --git a/packages/fuels-code-gen/src/program_bindings/custom_types/utils.rs b/packages/fuels-code-gen/src/program_bindings/custom_types/utils.rs index 03bdd2885a..b2fa3bef0a 100644 --- a/packages/fuels-code-gen/src/program_bindings/custom_types/utils.rs +++ b/packages/fuels-code-gen/src/program_bindings/custom_types/utils.rs @@ -1,31 +1,12 @@ use fuel_abi_types::{ abi::full_program::FullTypeDeclaration, - utils::{extract_generic_name, ident, TypePath}, + utils::{self, extract_generic_name}, }; -use proc_macro2::TokenStream; -use quote::quote; - -use crate::{error::Result, program_bindings::utils::Component}; - -/// Transforms components from inside the given `FullTypeDeclaration` into a vector -/// of `Components`. Will fail if there are no components. -pub(crate) fn extract_components( - type_decl: &FullTypeDeclaration, - snake_case: bool, - mod_name: &TypePath, -) -> Result> { - type_decl - .components - .iter() - .map(|component| Component::new(component, snake_case, mod_name.clone())) - .collect() -} +use proc_macro2::Ident; /// Returns a vector of TokenStreams, one for each of the generic parameters /// used by the given type. -pub(crate) fn extract_generic_parameters( - type_decl: &FullTypeDeclaration, -) -> Result> { +pub(crate) fn extract_generic_parameters(type_decl: &FullTypeDeclaration) -> Vec { type_decl .type_parameters .iter() @@ -33,18 +14,22 @@ pub(crate) fn extract_generic_parameters( let name = extract_generic_name(&decl.type_field).unwrap_or_else(|| { panic!("Type parameters should only contain ids of generic types!") }); - let generic = ident(&name); - Ok(quote! {#generic}) + utils::ident(&name) }) .collect() } #[cfg(test)] mod tests { - use fuel_abi_types::{abi::program::TypeDeclaration, utils::extract_custom_type_name}; + use fuel_abi_types::{ + abi::{full_program::FullTypeApplication, program::TypeDeclaration}, + utils::{extract_custom_type_name, TypePath}, + }; + use pretty_assertions::assert_eq; + use quote::quote; use super::*; - use crate::program_bindings::{resolved_type::ResolvedType, utils::param_type_calls}; + use crate::{error::Result, program_bindings::utils::Components}; #[test] fn extracts_generic_types() -> Result<()> { @@ -78,7 +63,7 @@ mod tests { let generics = extract_generic_parameters(&FullTypeDeclaration::from_counterpart( &declaration, &types, - ))?; + )); // then let stringified_generics = generics @@ -94,48 +79,75 @@ mod tests { #[test] fn param_type_calls_correctly_generated() { // arrange - let components = vec![ - Component { - field_name: ident("a"), - field_type: ResolvedType { - type_name: quote! {u8}, - generic_params: vec![], + let type_applications = vec![ + FullTypeApplication { + name: "unimportant".to_string(), + type_decl: FullTypeDeclaration { + type_field: "u8".to_string(), + components: vec![], + type_parameters: vec![], }, + type_arguments: vec![], }, - Component { - field_name: ident("b"), - field_type: ResolvedType { - type_name: quote! {SomeStruct}, - generic_params: vec![ - ResolvedType { - type_name: quote! {T}, - generic_params: vec![], + FullTypeApplication { + name: "unimportant".to_string(), + type_decl: FullTypeDeclaration { + type_field: "struct SomeStruct".to_string(), + components: vec![], + type_parameters: vec![ + FullTypeDeclaration { + type_field: "generic T".to_string(), + components: vec![], + type_parameters: vec![], }, - ResolvedType { - type_name: quote! {K}, - generic_params: vec![], + FullTypeDeclaration { + type_field: "generic K".to_string(), + components: vec![], + type_parameters: vec![], }, ], }, + type_arguments: vec![ + FullTypeApplication { + name: "unimportant".to_string(), + type_decl: FullTypeDeclaration { + type_field: "u8".to_string(), + components: vec![], + type_parameters: vec![], + }, + type_arguments: vec![], + }, + FullTypeApplication { + name: "unimportant".to_string(), + type_decl: FullTypeDeclaration { + type_field: "u16".to_string(), + components: vec![], + type_parameters: vec![], + }, + type_arguments: vec![], + }, + ], }, ]; // act - let result = param_type_calls(&components); + let param_type_calls = Components::new(&type_applications, true, TypePath::default()) + .unwrap() + .param_type_calls(); // assert - let stringified_result = result + let stringified_result = param_type_calls .into_iter() .map(|stream| stream.to_string()) .collect::>(); - assert_eq!( - stringified_result, - vec![ - "< u8 as :: fuels :: core :: traits :: Parameterize > :: param_type ()", - "< SomeStruct :: < T , K > as :: fuels :: core :: traits :: Parameterize > :: param_type ()" - ] - ) + + let expected = vec![ + quote! { <::core::primitive::u8 as :: fuels::core::traits::Parameterize>::param_type() }.to_string(), + quote! { as ::fuels::core::traits::Parameterize>::param_type() }.to_string(), + ]; + assert_eq!(stringified_result, expected); } + #[test] fn can_extract_struct_name() { let declaration = TypeDeclaration { diff --git a/packages/fuels-code-gen/src/program_bindings/resolved_type.rs b/packages/fuels-code-gen/src/program_bindings/resolved_type.rs index 5c49b64445..8bee4508dc 100644 --- a/packages/fuels-code-gen/src/program_bindings/resolved_type.rs +++ b/packages/fuels-code-gen/src/program_bindings/resolved_type.rs @@ -2,45 +2,89 @@ use std::fmt::{Display, Formatter}; use fuel_abi_types::{ abi::full_program::FullTypeApplication, - utils::{extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, + utils::{self, extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, }; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; use crate::{ error::{error, Result}, program_bindings::utils::sdk_provided_custom_types_lookup, - utils::{safe_ident, TypePath}, + utils::TypePath, }; +#[derive(Debug, Clone, PartialEq)] +pub enum GenericType { + Named(Ident), + Constant(usize), +} + +impl ToTokens for GenericType { + fn to_tokens(&self, tokens: &mut TokenStream) { + let stream = match self { + GenericType::Named(ident) => ident.to_token_stream(), + GenericType::Constant(constant) => constant.to_token_stream(), + }; + + tokens.extend(stream); + } +} + /// Represents a Rust type alongside its generic parameters. For when you want to reference an ABI /// type in Rust code since [`ResolvedType`] can be converted into a [`TokenStream`] via /// `resolved_type.to_token_stream()`. #[derive(Debug, Clone)] -pub struct ResolvedType { - pub type_name: TokenStream, - pub generic_params: Vec, +pub enum ResolvedType { + Unit, + Primitive(TypePath), + StructOrEnum { + path: TypePath, + generics: Vec, + }, + Array(Box, usize), + Tuple(Vec), + Generic(GenericType), } impl ResolvedType { - pub fn is_unit(&self) -> bool { - self.type_name.to_string() == "()" + pub fn generics(&self) -> Vec { + match self { + ResolvedType::StructOrEnum { + generics: elements, .. + } + | ResolvedType::Tuple(elements) => { + elements.iter().flat_map(|el| el.generics()).collect() + } + ResolvedType::Array(el, _) => el.generics(), + ResolvedType::Generic(inner) => vec![inner.clone()], + _ => vec![], + } } } impl ToTokens for ResolvedType { fn to_tokens(&self, tokens: &mut TokenStream) { - let type_name = &self.type_name; - - let tokenized_type = if self.generic_params.is_empty() { - type_name.clone() - } else { - let generic_params = self.generic_params.iter().map(ToTokens::to_token_stream); - - quote! { #type_name<#( #generic_params ),*> } + let tokenized = match self { + ResolvedType::Unit => quote! {()}, + ResolvedType::Primitive(path) => path.into_token_stream(), + ResolvedType::StructOrEnum { path, generics } => { + if generics.is_empty() { + path.to_token_stream() + } else { + quote! { #path<#(#generics),*>} + } + } + ResolvedType::Array(el, count) => quote! { [#el; #count]}, + ResolvedType::Tuple(elements) => { + // it is important to leave a trailing comma because a tuple with + // one element is written as (element,) not (element) which is + // resolved to just element + quote! { (#(#elements,)*) } + } + ResolvedType::Generic(generic_type) => generic_type.into_token_stream(), }; - tokens.extend(tokenized_type) + tokens.extend(tokenized) } } @@ -69,15 +113,15 @@ impl TypeResolver { pub(crate) fn resolve(&self, type_application: &FullTypeApplication) -> Result { let resolvers = [ - Self::to_simple_type, - Self::to_bits256, - Self::to_generic, - Self::to_array, - Self::to_sized_ascii_string, - Self::to_ascii_string, - Self::to_tuple, - Self::to_raw_slice, - Self::to_custom_type, + Self::try_as_primitive_type, + Self::try_as_bits256, + Self::try_as_generic, + Self::try_as_array, + Self::try_as_sized_ascii_string, + Self::try_as_ascii_string, + Self::try_as_tuple, + Self::try_as_raw_slice, + Self::try_as_custom_type, ]; for resolver in resolvers { @@ -100,19 +144,19 @@ impl TypeResolver { .collect() } - fn to_generic(&self, type_application: &FullTypeApplication) -> Result> { + fn try_as_generic( + &self, + type_application: &FullTypeApplication, + ) -> Result> { let Some(name) = extract_generic_name(&type_application.type_decl.type_field) else { return Ok(None); }; - let type_name = safe_ident(&name).into_token_stream(); - Ok(Some(ResolvedType { - type_name, - generic_params: vec![], - })) + let ident = utils::safe_ident(&name); + Ok(Some(ResolvedType::Generic(GenericType::Named(ident)))) } - fn to_array(&self, type_application: &FullTypeApplication) -> Result> { + fn try_as_array(&self, type_application: &FullTypeApplication) -> Result> { let type_decl = &type_application.type_decl; let Some(len) = extract_array_len(&type_decl.type_field) else { return Ok(None); @@ -128,13 +172,13 @@ impl TypeResolver { } }; - Ok(Some(ResolvedType { - type_name: quote! { [#type_inside; #len] }, - generic_params: vec![], - })) + Ok(Some(ResolvedType::Array( + Box::new(type_inside.clone()), + len, + ))) } - fn to_sized_ascii_string( + fn try_as_sized_ascii_string( &self, type_application: &FullTypeApplication, ) -> Result> { @@ -142,89 +186,95 @@ impl TypeResolver { return Ok(None); }; - let generic_params = vec![ResolvedType { - type_name: quote! {#len}, - generic_params: vec![], - }]; - - Ok(Some(ResolvedType { - type_name: quote! { ::fuels::types::SizedAsciiString }, - generic_params, + let path = + TypePath::new("::fuels::types::SizedAsciiString").expect("this is a valid TypePath"); + Ok(Some(ResolvedType::StructOrEnum { + path, + generics: vec![ResolvedType::Generic(GenericType::Constant(len))], })) } - fn to_ascii_string( + fn try_as_ascii_string( &self, type_application: &FullTypeApplication, ) -> Result> { - if type_application.type_decl.type_field == "str" { - Ok(Some(ResolvedType { - type_name: quote! { ::fuels::types::AsciiString }, - generic_params: vec![], - })) - } else { - Ok(None) - } + let maybe_resolved = (type_application.type_decl.type_field == "str").then(|| { + let path = + TypePath::new("::fuels::types::AsciiString").expect("this is a valid TypePath"); + ResolvedType::StructOrEnum { + path, + generics: vec![], + } + }); + + Ok(maybe_resolved) } - fn to_tuple(&self, type_application: &FullTypeApplication) -> Result> { + fn try_as_tuple(&self, type_application: &FullTypeApplication) -> Result> { let type_decl = &type_application.type_decl; if !has_tuple_format(&type_decl.type_field) { return Ok(None); } let inner_types = self.resolve_multiple(&type_decl.components)?; - // it is important to leave a trailing comma because a tuple with - // one element is written as (element,) not (element) which is - // resolved to just element - Ok(Some(ResolvedType { - type_name: quote! {(#(#inner_types,)*)}, - generic_params: vec![], - })) + Ok(Some(ResolvedType::Tuple(inner_types))) } - fn to_simple_type(&self, type_decl: &FullTypeApplication) -> Result> { + fn try_as_primitive_type( + &self, + type_decl: &FullTypeApplication, + ) -> Result> { let type_field = &type_decl.type_decl.type_field; - match type_field.as_str() { - "u8" | "u16" | "u32" | "u64" | "bool" | "()" => { - let type_name = type_field - .parse() - .expect("Couldn't resolve primitive type. Cannot happen!"); + let maybe_resolved = match type_field.as_str() { + "()" => Some(ResolvedType::Unit), + "struct std::u128::U128" | "struct U128" => { + let u128_path = TypePath::new("::core::primitive::u128").expect("to be correct"); + Some(ResolvedType::Primitive(u128_path)) + } + "u8" | "u16" | "u32" | "u64" | "bool" => { + let path = format!("::core::primitive::{type_field}"); + let type_path = TypePath::new(path).expect("to be a valid path"); - Ok(Some(ResolvedType { - type_name, - generic_params: vec![], - })) + Some(ResolvedType::Primitive(type_path)) } - _ => Ok(None), - } + _ => None, + }; + + Ok(maybe_resolved) } - fn to_bits256(&self, type_application: &FullTypeApplication) -> Result> { + fn try_as_bits256( + &self, + type_application: &FullTypeApplication, + ) -> Result> { if type_application.type_decl.type_field != "b256" { return Ok(None); } - Ok(Some(ResolvedType { - type_name: quote! {::fuels::types::Bits256}, - generic_params: vec![], + let path = TypePath::new("::fuels::types::Bits256").expect("to be valid"); + Ok(Some(ResolvedType::StructOrEnum { + path, + generics: vec![], })) } - fn to_raw_slice(&self, type_application: &FullTypeApplication) -> Result> { + fn try_as_raw_slice( + &self, + type_application: &FullTypeApplication, + ) -> Result> { if type_application.type_decl.type_field != "raw untyped slice" { return Ok(None); } - let type_name = quote! {::fuels::types::RawSlice}; - Ok(Some(ResolvedType { - type_name, - generic_params: vec![], + let path = TypePath::new("::fuels::types::RawSlice").expect("this is a valid TypePath"); + Ok(Some(ResolvedType::StructOrEnum { + path, + generics: vec![], })) } - fn to_custom_type( + fn try_as_custom_type( &self, type_application: &FullTypeApplication, ) -> Result> { @@ -234,33 +284,67 @@ impl TypeResolver { return Ok(None); } - let type_path = type_decl.custom_type_path()?; + let original_path = type_decl.custom_type_path()?; - let type_path = sdk_provided_custom_types_lookup() - .get(&type_path) + let used_path = sdk_provided_custom_types_lookup() + .get(&original_path) .cloned() - .unwrap_or_else(|| type_path.relative_path_from(&self.current_mod)); + .unwrap_or_else(|| original_path.relative_path_from(&self.current_mod)); - let generic_params = self.resolve_multiple(&type_application.type_arguments)?; + let generics = self.resolve_multiple(&type_application.type_arguments)?; - Ok(Some(ResolvedType { - type_name: type_path.into_token_stream(), - generic_params, + Ok(Some(ResolvedType::StructOrEnum { + path: used_path, + generics, })) } } #[cfg(test)] mod tests { - use std::collections::HashMap; - - use fuel_abi_types::abi::{ - full_program::FullTypeDeclaration, - program::{TypeApplication, TypeDeclaration}, + use std::{collections::HashMap, str::FromStr}; + + use fuel_abi_types::{ + abi::{ + full_program::FullTypeDeclaration, + program::{TypeApplication, TypeDeclaration}, + }, + utils::ident, }; use super::*; + #[test] + fn correctly_extracts_used_generics() { + let resolved_type = ResolvedType::StructOrEnum { + path: Default::default(), + generics: vec![ + ResolvedType::Tuple(vec![ResolvedType::Array( + Box::new(ResolvedType::StructOrEnum { + path: Default::default(), + generics: vec![ + ResolvedType::Generic(GenericType::Named(ident("A"))), + ResolvedType::Generic(GenericType::Constant(10)), + ], + }), + 2, + )]), + ResolvedType::Generic(GenericType::Named(ident("B"))), + ], + }; + + let generics = resolved_type.generics(); + + assert_eq!( + generics, + vec![ + GenericType::Named(ident("A")), + GenericType::Constant(10), + GenericType::Named(ident("B")) + ] + ) + } + fn test_resolve_first_type( expected: &str, type_declarations: &[TypeDeclaration], @@ -280,6 +364,7 @@ mod tests { .map_err(|e| e.combine(error!("failed to resolve {:?}", type_application)))?; let actual = resolved_type.to_token_stream().to_string(); + let expected = TokenStream::from_str(expected).unwrap().to_string(); assert_eq!(actual, expected); Ok(()) @@ -298,32 +383,32 @@ mod tests { #[test] fn test_resolve_u8() -> Result<()> { - test_resolve_primitive_type("u8", "u8") + test_resolve_primitive_type("u8", "::core::primitive::u8") } #[test] fn test_resolve_u16() -> Result<()> { - test_resolve_primitive_type("u16", "u16") + test_resolve_primitive_type("u16", "::core::primitive::u16") } #[test] fn test_resolve_u32() -> Result<()> { - test_resolve_primitive_type("u32", "u32") + test_resolve_primitive_type("u32", "::core::primitive::u32") } #[test] fn test_resolve_u64() -> Result<()> { - test_resolve_primitive_type("u64", "u64") + test_resolve_primitive_type("u64", "::core::primitive::u64") } #[test] fn test_resolve_bool() -> Result<()> { - test_resolve_primitive_type("bool", "bool") + test_resolve_primitive_type("bool", "::core::primitive::bool") } #[test] fn test_resolve_b256() -> Result<()> { - test_resolve_primitive_type("b256", ":: fuels :: types :: Bits256") + test_resolve_primitive_type("b256", "::fuels::types::Bits256") } #[test] @@ -334,7 +419,7 @@ mod tests { #[test] fn test_resolve_array() -> Result<()> { test_resolve_first_type( - "[u8 ; 3usize]", + "[::core::primitive::u8 ; 3usize]", &[ TypeDeclaration { type_id: 0, @@ -626,7 +711,7 @@ mod tests { #[test] fn test_resolve_tuple() -> Result<()> { test_resolve_first_type( - "(u8 , u16 , bool , T ,)", + "(::core::primitive::u8, ::core::primitive::u16, ::core::primitive::bool, T,)", &[ TypeDeclaration { type_id: 0, @@ -677,17 +762,18 @@ mod tests { #[test] fn custom_types_uses_correct_path_for_sdk_provided_types() { + let resolver = TypeResolver::default(); for (type_path, expected_path) in sdk_provided_custom_types_lookup() { // given let type_application = given_fn_arg_of_custom_type(&type_path); // when - let resolved_type = TypeResolver::default().resolve(&type_application).unwrap(); + let resolved_type = resolver.resolve(&type_application).unwrap(); // then let expected_type_name = expected_path.into_token_stream(); assert_eq!( - resolved_type.type_name.to_string(), + resolved_type.to_token_stream().to_string(), expected_type_name.to_string() ); } diff --git a/packages/fuels-code-gen/src/program_bindings/utils.rs b/packages/fuels-code-gen/src/program_bindings/utils.rs index c6cc2404ad..3e26af71bd 100644 --- a/packages/fuels-code-gen/src/program_bindings/utils.rs +++ b/packages/fuels-code-gen/src/program_bindings/utils.rs @@ -1,69 +1,142 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use fuel_abi_types::abi::full_program::FullTypeApplication; use inflector::Inflector; +use itertools::Itertools; use proc_macro2::{Ident, TokenStream}; -use quote::{quote, ToTokens}; +use quote::quote; +use super::resolved_type::GenericType; use crate::{ error::Result, program_bindings::resolved_type::{ResolvedType, TypeResolver}, - utils::{safe_ident, TypePath}, + utils::{self, safe_ident, TypePath}, }; -// Represents a component of either a struct(field name) or an enum(variant -// name). #[derive(Debug)] -pub(crate) struct Component { - pub field_name: Ident, - pub field_type: ResolvedType, +pub(crate) struct Components { + components: Vec<(Ident, ResolvedType)>, } -impl Component { +impl Components { pub fn new( - component: &FullTypeApplication, + type_applications: &[FullTypeApplication], snake_case: bool, - mod_of_component: TypePath, - ) -> Result { - let field_name = if snake_case { - component.name.to_snake_case() - } else { - component.name.to_owned() - }; - - Ok(Component { - field_name: safe_ident(&field_name), - field_type: TypeResolver::new(mod_of_component).resolve(component)?, + parent_module: TypePath, + ) -> Result { + let type_resolver = TypeResolver::new(parent_module); + let components = type_applications + .iter() + .map(|type_application| { + let name = if snake_case { + type_application.name.to_snake_case() + } else { + type_application.name.to_owned() + }; + + let ident = safe_ident(&name); + let ty = type_resolver.resolve(type_application)?; + Result::Ok((ident, ty)) + }) + .collect::>>()?; + + Ok(Self { components }) + } + + pub fn iter(&self) -> impl Iterator { + self.components.iter().map(|(ident, ty)| (ident, ty)) + } + + pub fn is_empty(&self) -> bool { + self.components.is_empty() + } + + pub fn as_enum_variants(&self) -> impl Iterator + '_ { + self.components.iter().map(|(ident, ty)| { + if let ResolvedType::Unit = ty { + quote! {#ident} + } else { + quote! {#ident(#ty)} + } }) } -} -/// Returns TokenStreams representing calls to `Parameterize::param_type` for -/// all given Components. Makes sure to properly handle calls when generics are -/// involved. -pub(crate) fn param_type_calls(field_entries: &[Component]) -> Vec { - field_entries - .iter() - .map(|Component { field_type, .. }| single_param_type_call(field_type)) - .collect() + pub fn generate_parameters_for_unused_generics( + &self, + declared_generics: &[Ident], + ) -> (Vec, Vec) { + self.unused_named_generics(declared_generics) + .enumerate() + .map(|(index, generic)| { + let ident = utils::ident(&format!("_unused_generic_{index}")); + let ty = quote! {::core::marker::PhantomData<#generic>}; + (ident, ty) + }) + .unzip() + } + + pub fn generate_variant_for_unused_generics( + &self, + declared_generics: &[Ident], + ) -> Option { + let phantom_types = self + .unused_named_generics(declared_generics) + .map(|generic| { + quote! {::core::marker::PhantomData<#generic>} + }) + .collect_vec(); + + (!phantom_types.is_empty()).then(|| { + quote! { + #[Ignore] + IgnoreMe(#(#phantom_types),*) + } + }) + } + + pub fn param_type_calls(&self) -> Vec { + self.components + .iter() + .map(|(_, ty)| { + quote! { <#ty as ::fuels::core::traits::Parameterize>::param_type() } + }) + .collect() + } + + fn named_generics(&self) -> HashSet { + self.components + .iter() + .flat_map(|(_, ty)| ty.generics()) + .filter_map(|generic_type| { + if let GenericType::Named(name) = generic_type { + Some(name) + } else { + None + } + }) + .collect() + } + + fn unused_named_generics<'a>( + &'a self, + declared_generics: &'a [Ident], + ) -> impl Iterator { + let used_generics = self.named_generics(); + declared_generics + .iter() + .filter(move |generic| !used_generics.contains(generic)) + } } -/// Returns a TokenStream representing the call to `Parameterize::param_type` for -/// the given ResolvedType. Makes sure to properly handle calls when generics are -/// involved. -pub(crate) fn single_param_type_call(field_type: &ResolvedType) -> TokenStream { - let type_name = &field_type.type_name; - let parameters = field_type - .generic_params - .iter() - .map(|resolved_type| resolved_type.to_token_stream()) - .collect::>(); - - if parameters.is_empty() { - quote! { <#type_name as ::fuels::core::traits::Parameterize>::param_type() } - } else { - quote! { <#type_name::<#(#parameters),*> as ::fuels::core::traits::Parameterize>::param_type() } +pub(crate) fn tokenize_generics(generics: &[Ident]) -> (TokenStream, TokenStream) { + if generics.is_empty() { + return (Default::default(), Default::default()); } + + ( + quote! {<#(#generics,)*>}, + quote! {<#(#generics: ::fuels::core::traits::Tokenizable + ::fuels::core::traits::Parameterize, )*>}, + ) } #[cfg(test)] @@ -74,11 +147,14 @@ mod tests { #[test] fn respects_snake_case_flag() -> Result<()> { + // given let type_application = type_application_named("WasNotSnakeCased"); - let sut = Component::new(&type_application, true, TypePath::default())?; + // when + let sut = Components::new(&[type_application], true, TypePath::default())?; - assert_eq!(sut.field_name, "was_not_snake_cased"); + // then + assert_eq!(sut.iter().next().unwrap().0, "was_not_snake_cased"); Ok(()) } @@ -88,17 +164,17 @@ mod tests { { let type_application = type_application_named("if"); - let sut = Component::new(&type_application, false, TypePath::default())?; + let sut = Components::new(&[type_application], false, TypePath::default())?; - assert_eq!(sut.field_name, "if_"); + assert_eq!(sut.iter().next().unwrap().0, "if_"); } { let type_application = type_application_named("let"); - let sut = Component::new(&type_application, false, TypePath::default())?; + let sut = Components::new(&[type_application], false, TypePath::default())?; - assert_eq!(sut.field_name, "let_"); + assert_eq!(sut.iter().next().unwrap().0, "let_"); } Ok(()) @@ -128,7 +204,6 @@ pub(crate) fn sdk_provided_custom_types_lookup() -> HashMap ("std::option::Option", "::core::option::Option"), ("std::result::Result", "::core::result::Result"), ("std::string::String", "::std::string::String"), - ("std::u128::U128", "u128"), ("std::u256::U256", "::fuels::types::U256"), ("std::vec::Vec", "::std::vec::Vec"), ( @@ -158,12 +233,14 @@ pub(crate) fn sdk_provided_custom_types_lookup() -> HashMap .collect() } -pub(crate) fn get_equivalent_bech32_type(ttype: &str) -> Option { - match ttype { - ":: fuels :: types :: Address" => Some(quote! {::fuels::types::bech32::Bech32Address}), - ":: fuels :: types :: ContractId" => { - Some(quote! {::fuels::types::bech32::Bech32ContractId}) - } +pub(crate) fn get_equivalent_bech32_type(ttype: &ResolvedType) -> Option { + let ResolvedType::StructOrEnum { path, .. } = ttype else { + return None; + }; + + match path.to_string().as_str() { + "::fuels::types::Address" => Some(quote! {::fuels::types::bech32::Bech32Address}), + "::fuels::types::ContractId" => Some(quote! {::fuels::types::bech32::Bech32ContractId}), _ => None, } } diff --git a/packages/fuels-core/src/codec/abi_decoder.rs b/packages/fuels-core/src/codec/abi_decoder.rs index a8a2f9baf4..c274bf09a3 100644 --- a/packages/fuels-core/src/codec/abi_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder.rs @@ -84,10 +84,9 @@ mod tests { use std::vec; use super::*; - use crate::types::U256; use crate::{ constants::WORD_SIZE, - types::{enum_variants::EnumVariants, errors::Error, StaticStringToken}, + types::{enum_variants::EnumVariants, errors::Error, StaticStringToken, U256}, }; #[test] diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index 7818e19449..1bda8206b1 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -1,10 +1,10 @@ -use itertools::chain; use std::{collections::HashMap, iter::zip}; use fuel_abi_types::{ abi::program::{TypeApplication, TypeDeclaration}, utils::{extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, }; +use itertools::chain; use crate::{ constants::WORD_SIZE, diff --git a/packages/fuels-core/src/types/transaction_builders.rs b/packages/fuels-core/src/types/transaction_builders.rs index 375b44afe5..1c9639c43e 100644 --- a/packages/fuels-core/src/types/transaction_builders.rs +++ b/packages/fuels-core/src/types/transaction_builders.rs @@ -13,6 +13,7 @@ use fuel_types::{bytes::padded_len_usize, Bytes32, ChainId, MemLayout, Salt}; use fuel_vm::{checked_transaction::EstimatePredicates, gas::GasCosts}; use zeroize::{Zeroize, ZeroizeOnDrop}; +use super::{chain_info::ChainInfo, node_info::NodeInfo}; use crate::{ constants::{BASE_ASSET_ID, WORD_SIZE}, offsets, @@ -29,8 +30,6 @@ use crate::{ }, }; -use super::{chain_info::ChainInfo, node_info::NodeInfo}; - #[derive(Debug, Clone)] pub struct NetworkInfo { pub consensus_parameters: ConsensusParameters, diff --git a/packages/fuels-macros/src/derive/parameterize.rs b/packages/fuels-macros/src/derive/parameterize.rs index 74b1901d7c..b71f169534 100644 --- a/packages/fuels-macros/src/derive/parameterize.rs +++ b/packages/fuels-macros/src/derive/parameterize.rs @@ -4,9 +4,7 @@ use syn::{Data, DataEnum, DataStruct, DeriveInput, Error, Generics, Result}; use crate::{ derive::utils::{find_attr, get_path_from_attr_or, std_lib_path}, - parse_utils::{ - extract_enum_members, extract_struct_members, validate_and_extract_generic_types, - }, + parse_utils::{validate_and_extract_generic_types, Members}, }; pub fn generate_parameterize_impl(input: DeriveInput) -> Result { @@ -46,7 +44,7 @@ fn parameterize_for_struct( no_std: bool, ) -> Result { let (impl_gen, type_gen, where_clause) = generics.split_for_impl(); - let members = extract_struct_members(contents, fuels_core_path.clone())?; + let members = Members::from_struct(contents, fuels_core_path.clone())?; let param_type_calls = members.param_type_calls(); let generic_param_types = parameterize_generic_params(&generics, &fuels_core_path)?; @@ -89,7 +87,7 @@ fn parameterize_for_enum( ) -> Result { let (impl_gen, type_gen, where_clause) = generics.split_for_impl(); let enum_name_str = name.to_string(); - let members = extract_enum_members(contents, fuels_core_path.clone())?; + let members = Members::from_enum(contents, fuels_core_path.clone())?; let variant_param_types = members.param_type_calls(); let generic_param_types = parameterize_generic_params(&generics, &fuels_core_path)?; diff --git a/packages/fuels-macros/src/derive/tokenizable.rs b/packages/fuels-macros/src/derive/tokenizable.rs index 6128443581..c6ac9b5c5e 100644 --- a/packages/fuels-macros/src/derive/tokenizable.rs +++ b/packages/fuels-macros/src/derive/tokenizable.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{Data, DataEnum, DataStruct, DeriveInput, Error, Generics, Result}; @@ -7,7 +8,7 @@ use crate::{ utils, utils::{find_attr, get_path_from_attr_or, std_lib_path}, }, - parse_utils::{extract_struct_members, validate_and_extract_generic_types}, + parse_utils::{validate_and_extract_generic_types, Members}, }; pub fn generate_tokenizable_impl(input: DeriveInput) -> Result { @@ -46,12 +47,13 @@ fn tokenizable_for_struct( fuels_core_path: TokenStream, no_std: bool, ) -> Result { + validate_and_extract_generic_types(&generics)?; let (impl_gen, type_gen, where_clause) = generics.split_for_impl(); let struct_name_str = name.to_string(); - let members = extract_struct_members(contents, fuels_core_path.clone())?; + let members = Members::from_struct(contents, fuels_core_path.clone())?; let field_names = members.names().collect::>(); + let ignored_field_names = members.ignored_names().collect_vec(); - validate_and_extract_generic_types(&generics)?; let std_lib = std_lib_path(no_std); Ok(quote! { @@ -71,8 +73,9 @@ fn tokenizable_for_struct( }; ::core::result::Result::Ok(Self { #( - #field_names: #fuels_core_path::traits::Tokenizable::from_token(next_token()?)? - ),* + #field_names: #fuels_core_path::traits::Tokenizable::from_token(next_token()?)?, + )* + #(#ignored_field_names: ::core::default::Default::default(),)* }) }, @@ -91,14 +94,13 @@ fn tokenizable_for_enum( fuels_core_path: TokenStream, no_std: bool, ) -> Result { + validate_and_extract_generic_types(&generics)?; let (impl_gen, type_gen, where_clause) = generics.split_for_impl(); let name_stringified = name.to_string(); - let variants = utils::extract_variants(&contents.variants, fuels_core_path.clone())?; + let variants = utils::extract_variants(contents.variants, fuels_core_path.clone())?; let discriminant_and_token = variants.variant_into_discriminant_and_token(); let constructed_variant = variants.variant_from_discriminant_and_token(no_std); - validate_and_extract_generic_types(&generics)?; - let std_lib = std_lib_path(no_std); Ok(quote! { diff --git a/packages/fuels-macros/src/derive/utils.rs b/packages/fuels-macros/src/derive/utils.rs index e0a078d046..a0298f9c12 100644 --- a/packages/fuels-macros/src/derive/utils.rs +++ b/packages/fuels-macros/src/derive/utils.rs @@ -1,7 +1,9 @@ use fuels_code_gen::utils::TypePath; use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; -use syn::{Attribute, Error, Expr, ExprLit, Fields, Lit, Meta, Result, Type, Variant}; +use syn::{Attribute, Error, Expr, ExprLit, Fields, Lit, Meta, Result, Variant}; + +use crate::parse_utils::has_ignore_attr; pub(crate) fn get_path_from_attr_or( attr_name: &str, @@ -44,27 +46,46 @@ pub(crate) fn find_attr<'a>(name: &str, attrs: &'a [Attribute]) -> Option<&'a At }) } -pub(crate) struct ExtractedVariant { +pub(crate) struct VariantInfo { name: Ident, - discriminant: u8, is_unit: bool, } -pub(crate) fn extract_variants<'a>( - contents: impl IntoIterator, +pub(crate) enum ExtractedVariant { + Normal { info: VariantInfo, discriminant: u8 }, + Ignored { info: VariantInfo }, +} + +pub(crate) fn extract_variants( + contents: impl IntoIterator, fuels_core_path: TokenStream, ) -> Result { + let mut discriminant = 0; let variants = contents .into_iter() - .enumerate() - .map(|(discriminant, variant)| -> Result<_> { - let name = variant.ident.clone(); - let ty = get_variant_type(variant)?; - Ok(ExtractedVariant { - name, - discriminant: discriminant as u8, - is_unit: ty.is_none(), - }) + .map(|variant| -> Result<_> { + let is_unit = matches!(variant.fields, Fields::Unit); + if has_ignore_attr(&variant.attrs) { + Ok(ExtractedVariant::Ignored { + info: VariantInfo { + name: variant.ident, + is_unit, + }, + }) + } else { + validate_variant_type(&variant)?; + + let current_discriminant = discriminant; + discriminant += 1; + + Ok(ExtractedVariant::Normal { + info: VariantInfo { + name: variant.ident, + is_unit, + }, + discriminant: current_discriminant, + }) + } }) .collect::>()?; @@ -81,16 +102,29 @@ pub(crate) struct ExtractedVariants { impl ExtractedVariants { pub(crate) fn variant_into_discriminant_and_token(&self) -> TokenStream { - let match_branches = self.variants.iter().map(|variant| { - let discriminant = variant.discriminant; - let name = &variant.name; - let fuels_core_path = &self.fuels_core_path; - if variant.is_unit { - quote! { Self::#name => (#discriminant, #fuels_core_path::traits::Tokenizable::into_token(())) } - } else { - quote! { Self::#name(inner) => (#discriminant, #fuels_core_path::traits::Tokenizable::into_token(inner))} + let match_branches = self.variants.iter().map(|variant| + match variant { + ExtractedVariant::Normal { info: VariantInfo{ name, is_unit }, discriminant } => { + let fuels_core_path = &self.fuels_core_path; + if *is_unit { + quote! { Self::#name => (#discriminant, #fuels_core_path::traits::Tokenizable::into_token(())) } + } else { + quote! { Self::#name(inner) => (#discriminant, #fuels_core_path::traits::Tokenizable::into_token(inner))} + } + }, + ExtractedVariant::Ignored { info: VariantInfo{ name, is_unit } } => { + let panic_expression = { + let name_stringified = name.to_string(); + quote! {::core::panic!("Variant '{}' should never be constructed.", #name_stringified)} + }; + if *is_unit { + quote! { Self::#name => #panic_expression } + } else { + quote! { Self::#name(..) => #panic_expression } + } + } } - }); + ); quote! { match self { @@ -99,19 +133,24 @@ impl ExtractedVariants { } } pub(crate) fn variant_from_discriminant_and_token(&self, no_std: bool) -> TokenStream { - let match_discriminant = self.variants.iter().map(|variant| { - let name = &variant.name; - let discriminant = variant.discriminant; - let fuels_core_path = &self.fuels_core_path; + let match_discriminant = self + .variants + .iter() + .filter_map(|variant| match variant { + ExtractedVariant::Normal { info, discriminant } => Some((info, discriminant)), + _ => None, + }) + .map(|(VariantInfo { name, is_unit }, discriminant)| { + let fuels_core_path = &self.fuels_core_path; - let variant_value = if variant.is_unit { - quote! {} - } else { - quote! { (#fuels_core_path::traits::Tokenizable::from_token(variant_token)?) } - }; + let variant_value = if *is_unit { + quote! {} + } else { + quote! { (#fuels_core_path::traits::Tokenizable::from_token(variant_token)?) } + }; - quote! { #discriminant => ::core::result::Result::Ok(Self::#name #variant_value)} - }); + quote! { #discriminant => ::core::result::Result::Ok(Self::#name #variant_value)} + }); let std_lib = std_lib_path(no_std); quote! { @@ -125,26 +164,28 @@ impl ExtractedVariants { } } -fn get_variant_type(variant: &Variant) -> Result> { +fn validate_variant_type(variant: &Variant) -> Result<()> { match &variant.fields { - Fields::Named(named_fields) => Err(Error::new_spanned( - named_fields.clone(), - "Struct like enum variants are not supported".to_string(), - )), + Fields::Named(named_fields) => { + return Err(Error::new_spanned( + named_fields.clone(), + "Struct like enum variants are not supported".to_string(), + )) + } Fields::Unnamed(unnamed_fields) => { let fields = &unnamed_fields.unnamed; - if fields.len() == 1 { - Ok(fields.iter().next().map(|field| &field.ty)) - } else { - Err(Error::new_spanned( + if fields.len() != 1 { + return Err(Error::new_spanned( unnamed_fields.clone(), "Tuple-like enum variants must contain exactly one element.".to_string(), - )) + )); } } - Fields::Unit => Ok(None), + _ => {} } + + Ok(()) } pub(crate) fn std_lib_path(no_std: bool) -> TokenStream { diff --git a/packages/fuels-macros/src/lib.rs b/packages/fuels-macros/src/lib.rs index 6ea8201dc3..237628446f 100644 --- a/packages/fuels-macros/src/lib.rs +++ b/packages/fuels-macros/src/lib.rs @@ -58,7 +58,7 @@ pub fn setup_program_test(input: TokenStream) -> TokenStream { .into() } -#[proc_macro_derive(Parameterize, attributes(FuelsTypesPath, FuelsCorePath, NoStd))] +#[proc_macro_derive(Parameterize, attributes(FuelsTypesPath, FuelsCorePath, NoStd, Ignore))] pub fn parameterize(stream: TokenStream) -> TokenStream { let input = parse_macro_input!(stream as DeriveInput); @@ -67,7 +67,7 @@ pub fn parameterize(stream: TokenStream) -> TokenStream { .into() } -#[proc_macro_derive(Tokenizable, attributes(FuelsTypesPath, FuelsCorePath, NoStd))] +#[proc_macro_derive(Tokenizable, attributes(FuelsTypesPath, FuelsCorePath, NoStd, Ignore))] pub fn tokenizable(stream: TokenStream) -> TokenStream { let input = parse_macro_input!(stream as DeriveInput); diff --git a/packages/fuels-macros/src/parse_utils.rs b/packages/fuels-macros/src/parse_utils.rs index eaf6a50a30..c340328465 100644 --- a/packages/fuels-macros/src/parse_utils.rs +++ b/packages/fuels-macros/src/parse_utils.rs @@ -1,8 +1,10 @@ pub(crate) use command::Command; -use itertools::{chain, process_results, Itertools}; +use itertools::{chain, Itertools}; use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; -use syn::{DataEnum, DataStruct, Error, Fields, GenericParam, Generics, TypeParam, Variant}; +use syn::{ + Attribute, DataEnum, DataStruct, Error, Fields, GenericParam, Generics, TypeParam, Variant, +}; pub(crate) use unique_lit_strs::UniqueLitStrs; pub(crate) use unique_name_values::UniqueNameValues; @@ -99,94 +101,127 @@ pub fn validate_and_extract_generic_types(generics: &Generics) -> syn::Result, - types: Vec, + members: Vec, fuels_core_path: TokenStream, } impl Members { - pub(crate) fn names(&self) -> impl Iterator + '_ { - self.names.iter() - } + pub(crate) fn from_struct( + fields: DataStruct, + fuels_core_path: TokenStream, + ) -> syn::Result { + let named_fields = match fields.fields { + Fields::Named(named_fields) => Ok(named_fields.named), + Fields::Unnamed(fields) => Err(Error::new_spanned( + fields.unnamed, + "Tuple-like structs not supported", + )), + _ => { + panic!("This cannot happen in valid Rust code. Fields::Unit only appears in enums") + } + }?; + + let members = named_fields + .into_iter() + .map(|field| { + let name = field + .ident + .expect("FieldsNamed to only contain named fields."); + if has_ignore_attr(&field.attrs) { + Member::Ignored { name } + } else { + let ty = field.ty.into_token_stream(); + Member::Normal { name, ty } + } + }) + .collect(); - pub(crate) fn param_type_calls(&self) -> impl Iterator + '_ { - let fuels_core_path = self.fuels_core_path.to_token_stream(); - self.types.iter().map(move |ty| { - quote! { <#ty as #fuels_core_path::traits::Parameterize>::param_type() } + Ok(Members { + members, + fuels_core_path, }) } -} -pub(crate) fn extract_struct_members( - fields: DataStruct, - fuels_core_path: TokenStream, -) -> syn::Result { - let named_fields = match fields.fields { - Fields::Named(named_fields) => Ok(named_fields.named), - Fields::Unnamed(fields) => Err(Error::new_spanned( - fields.unnamed, - "Tuple-like structs not supported", - )), - _ => { - panic!("This cannot happen in valid Rust code. Fields::Unit only appears in enums") - } - }?; + pub(crate) fn from_enum(data: DataEnum, fuels_core_path: TokenStream) -> syn::Result { + let members = data + .variants + .into_iter() + .map(|variant: Variant| { + let name = variant.ident; + if has_ignore_attr(&variant.attrs) { + Ok(Member::Ignored { name }) + } else { + let ty = match variant.fields { + Fields::Unnamed(fields_unnamed) => { + if fields_unnamed.unnamed.len() != 1 { + return Err(Error::new( + fields_unnamed.paren_token.span.join(), + "Must have exactly one element", + )); + } + fields_unnamed.unnamed.into_iter().next() + } + Fields::Unit => None, + Fields::Named(named_fields) => { + return Err(Error::new_spanned( + named_fields, + "Struct-like enum variants are not supported.", + )) + } + } + .map(|field| field.ty.into_token_stream()) + .unwrap_or_else(|| quote! {()}); + Ok(Member::Normal { name, ty }) + } + }) + .collect::, _>>()?; - let (names, types) = named_fields - .into_iter() - .map(|field| { - let name = field - .ident - .expect("FieldsNamed to only contain named fields."); - let ty = field.ty.into_token_stream(); - (name, ty) + Ok(Members { + members, + fuels_core_path, }) - .unzip(); - - Ok(Members { - names, - types, - fuels_core_path, - }) -} + } -pub(crate) fn extract_enum_members( - data: DataEnum, - fuels_core_path: TokenStream, -) -> syn::Result { - let components = data.variants.into_iter().map(|variant: Variant| { - let name = variant.ident; - - let ttype = match variant.fields { - Fields::Unnamed(fields_unnamed) => { - if fields_unnamed.unnamed.len() != 1 { - return Err(Error::new( - fields_unnamed.paren_token.span.join(), - "Must have exactly one element", - )); - } - fields_unnamed.unnamed.into_iter().next() - } - Fields::Unit => None, - Fields::Named(named_fields) => { - return Err(Error::new_spanned( - named_fields, - "Struct-like enum variants are not supported.", - )) + pub(crate) fn names(&self) -> impl Iterator + '_ { + self.members.iter().filter_map(|member| { + if let Member::Normal { name, .. } = member { + Some(name) + } else { + None } - } - .map(|field| field.ty.into_token_stream()) - .unwrap_or_else(|| quote! {()}); + }) + } - Ok((name, ttype)) - }); + pub(crate) fn ignored_names(&self) -> impl Iterator + '_ { + self.members.iter().filter_map(|member| { + if let Member::Ignored { name } = member { + Some(name) + } else { + None + } + }) + } - let (names, types) = process_results(components, |iter| iter.unzip())?; + pub(crate) fn param_type_calls(&self) -> impl Iterator + '_ { + let fuels_core_path = self.fuels_core_path.to_token_stream(); + self.members.iter().filter_map(move |member| match member { + Member::Normal { ty, .. } => { + Some(quote! { <#ty as #fuels_core_path::traits::Parameterize>::param_type() }) + } + _ => None, + }) + } +} - Ok(Members { - names, - types, - fuels_core_path, +pub(crate) fn has_ignore_attr(attrs: &[Attribute]) -> bool { + attrs.iter().any(|attr| match &attr.meta { + syn::Meta::Path(path) => path.get_ident().is_some_and(|ident| ident == "Ignore"), + _ => false, }) } diff --git a/packages/fuels/Cargo.toml b/packages/fuels/Cargo.toml index dec59c44cc..94a67e281c 100644 --- a/packages/fuels/Cargo.toml +++ b/packages/fuels/Cargo.toml @@ -21,6 +21,7 @@ fuels-programs = { workspace = true, optional = true } fuels-test-helpers = { workspace = true, optional = true } [dev-dependencies] +fuels-code-gen = { workspace = true } chrono = { workspace = true } fuel-core = { workspace = true, default-features = false } fuel-core-types = { workspace = true } diff --git a/packages/fuels/tests/types/contracts/generics/src/main.sw b/packages/fuels/tests/types/contracts/generics/src/main.sw index f3d20aefbc..672810f4b7 100644 --- a/packages/fuels/tests/types/contracts/generics/src/main.sw +++ b/packages/fuels/tests/types/contracts/generics/src/main.sw @@ -2,6 +2,30 @@ contract; use std::hash::*; +struct StructOneUnusedGenericParam {} + +#[allow(dead_code)] +enum EnumOneUnusedGenericParam { + One: (), +} + +struct StructTwoUnusedGenericParams {} + +#[allow(dead_code)] +enum EnumTwoUnusedGenericParams { + One: (), +} + +struct StructUsedAndUnusedGenericParams { + field: K, +} + +#[allow(dead_code)] +enum EnumUsedAndUnusedGenericParams { + One: str[3], + Two: K, +} + struct SimpleGeneric { single_generic_param: T, } @@ -37,6 +61,9 @@ impl Hash for str[3] { } abi MyContract { + fn unused_generic_args(arg_1: StructOneUnusedGenericParam, arg_2: EnumOneUnusedGenericParam); + fn two_unused_generic_args(arg_1: StructTwoUnusedGenericParams, arg_2: EnumTwoUnusedGenericParams); + fn used_and_unused_generic_args(arg_1: StructUsedAndUnusedGenericParams, arg_2: EnumUsedAndUnusedGenericParams) -> (StructUsedAndUnusedGenericParams, EnumUsedAndUnusedGenericParams); fn struct_w_generic(arg1: SimpleGeneric) -> SimpleGeneric; fn struct_delegating_generic(arg1: PassTheGenericOn) -> PassTheGenericOn; fn struct_w_generic_in_array(arg1: StructWArrayGeneric) -> StructWArrayGeneric; @@ -48,6 +75,29 @@ abi MyContract { } impl MyContract for Contract { + fn unused_generic_args( + _arg_1: StructOneUnusedGenericParam, + _arg_2: EnumOneUnusedGenericParam, + ) {} + fn two_unused_generic_args( + _arg_1: StructTwoUnusedGenericParams, + _arg_2: EnumTwoUnusedGenericParams, + ) {} + fn used_and_unused_generic_args( + arg_1: StructUsedAndUnusedGenericParams, + arg_2: EnumUsedAndUnusedGenericParams, + ) -> (StructUsedAndUnusedGenericParams, EnumUsedAndUnusedGenericParams) { + assert_eq(arg_1.field, 10u8); + if let EnumUsedAndUnusedGenericParams::Two(val) = arg_2 { + assert_eq(val, 11u8); + } else { + require(false, "Expected the variant EnumUsedAndUnusedGenericParams::Two"); + } + ( + StructUsedAndUnusedGenericParams { field: 12u8 }, + EnumUsedAndUnusedGenericParams::Two(13u8), + ) + } fn struct_w_generic(arg1: SimpleGeneric) -> SimpleGeneric { let expected = SimpleGeneric { single_generic_param: 123u64, diff --git a/packages/fuels/tests/types_contracts.rs b/packages/fuels/tests/types_contracts.rs index a50ea9c913..3c174660a3 100644 --- a/packages/fuels/tests/types_contracts.rs +++ b/packages/fuels/tests/types_contracts.rs @@ -1482,6 +1482,31 @@ async fn generics_test() -> Result<()> { assert_eq!(result, arg1); } + { + contract_methods + .unused_generic_args( + StructOneUnusedGenericParam::default(), + EnumOneUnusedGenericParam::One, + ) + .call() + .await?; + + let (the_struct, the_enum) = contract_methods + .used_and_unused_generic_args( + StructUsedAndUnusedGenericParams::new(10u8), + EnumUsedAndUnusedGenericParams::Two(11u8), + ) + .call() + .await? + .value; + + assert_eq!(the_struct.field, 12u8); + if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum { + assert_eq!(val, 13) + } else { + panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two"); + } + } { // complex case let pass_through = PassTheGenericOn { diff --git a/scripts/versions-replacer/src/main.rs b/scripts/versions-replacer/src/main.rs index a5d420e1b1..ccae69988f 100644 --- a/scripts/versions-replacer/src/main.rs +++ b/scripts/versions-replacer/src/main.rs @@ -6,11 +6,10 @@ use color_eyre::{ Result, }; use regex::Regex; -use walkdir::WalkDir; - use versions_replacer::{ metadata::collect_versions_from_cargo_toml, replace::replace_versions_in_file, }; +use walkdir::WalkDir; #[derive(FromArgs)] /// Replace variables like '{{{{versions.fuels}}}}' with correct versions from Cargo.toml.