Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ resolver = "2"
members = [
"e2e",
"examples/codec",
"examples/codegen",
"examples/contracts",
"examples/cookbook",
"examples/debugging",
Expand Down Expand Up @@ -52,7 +53,7 @@ cynic = { version = "3.1.0", default-features = false }
test-case = { version = "3.3", default-features = false }
eth-keystore = "0.5.0"
flate2 = { version = "1.0", default-features = false }
fuel-abi-types = "0.15.3"
fuel-abi-types = "0.16.0"
futures = "0.3.29"
hex = { version = "0.4.3", default-features = false }
itertools = "0.12.0"
Expand Down
5 changes: 5 additions & 0 deletions e2e/sway/abi/contract_with_alias/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "contract_with_alias"
26 changes: 26 additions & 0 deletions e2e/sway/abi/contract_with_alias/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
contract;

pub type MyAlias = Vec<b256>;
pub type MyU64 = u64;
pub type MyTuple = (MyU64, MyU64);
pub type MyArray = [MyTuple; 2];

abi MyContract {
fn with_b256(b: b256) -> b256;
fn with_myalias_vec() -> MyAlias;
fn with_mytuple() -> MyTuple;
}

impl MyContract for Contract {
fn with_b256(b: b256) -> b256 {
b256::zero()
}

fn with_myalias_vec() -> MyAlias {
MyAlias::new()
}

fn with_mytuple() -> MyTuple {
(32, 64)
}
}
5 changes: 5 additions & 0 deletions e2e/sway/contracts/alias/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "alias"
20 changes: 20 additions & 0 deletions e2e/sway/contracts/alias/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
contract;

pub type MyAlias = b256;
pub type MyU64 = u64;
pub type MyTuple = (MyU64, MyU64);

abi MyContract {
fn with_myalias(b: MyAlias) -> MyAlias;
fn with_mytuple() -> MyTuple;
}

impl MyContract for Contract {
fn with_myalias(b: MyAlias) -> MyAlias {
b256::zero()
}

fn with_mytuple() -> MyTuple {
(32, 64)
}
}
30 changes: 30 additions & 0 deletions e2e/tests/alias.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use fuels::prelude::*;

#[tokio::test]
async fn test_alias() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/alias"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
);

// Make sure we can call the contract with multiple arguments
let contract_methods = contract_instance.methods();
use abigen_bindings::my_contract_mod::MyAlias;
let response = contract_methods
.with_myalias(MyAlias::zeroed())
.call()
.await?;

assert_eq!(response.value, MyAlias::zeroed());

Ok(())
}
15 changes: 15 additions & 0 deletions examples/codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "fuels-example-codegen"
version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
publish = false
repository = { workspace = true }
description = "Fuel Rust SDK codegen examples."

[dev-dependencies]
fuels = { workspace = true, features = ["default"] }
fuels-code-gen = { workspace = true }
tokio = { workspace = true, features = ["full"] }
22 changes: 22 additions & 0 deletions examples/codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
extern crate alloc;

#[cfg(test)]
mod tests {
#[test]
fn example_alias() {
use fuels::code_gen::*;

let target = AbigenTarget::new(
"MyContract".into(),
Abi::load_from(
"/home/joao/dev/fuels-rs/e2e/sway/contracts/alias/out/release/alias-abi.json",
)
// Abi::load_from("/home/joao/dev/sway/_test_aliases_abi/out/debug/test-case-abi.json")
.unwrap(),
ProgramType::Contract,
);
let targets = vec![target];

let _abigen = Abigen::generate(targets, false).expect("abigen generation failed");
}
}
23 changes: 23 additions & 0 deletions packages/fuels-code-gen/src/program_bindings/abigen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,15 @@ 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<AbigenTarget>, no_std: bool) -> Result<TokenStream> {
// eprintln!("{:#?}", targets);

let generated_code = Self::generate_code(no_std, targets)?;

// eprintln!(
// "========================== CODE: \n {:#}",
// generated_code.code()
// );

let use_statements = generated_code.use_statements_for_uniquely_named_types();

let code = if no_std {
Expand Down Expand Up @@ -73,9 +80,12 @@ impl Abigen {
.iter()
.flat_map(|abi| abi.source.abi.logged_types.clone())
.collect_vec();

let bindings = Self::generate_all_bindings(parsed_targets, no_std, &shared_types)?;
// eprintln!("bindings {:#}", bindings.code().to_string());

let shared_types = Self::generate_shared_types(shared_types, &logged_types, no_std)?;
eprintln!("shared_types {:#}", shared_types.code().to_string());

let mod_name = ident("abigen_bindings");
Ok(shared_types.merge(bindings).wrap_in_mod(mod_name))
Expand All @@ -99,16 +109,20 @@ impl Abigen {
no_std: bool,
shared_types: &HashSet<FullTypeDeclaration>,
) -> Result<GeneratedCode> {
//eprintln!("generate_bindings shared_types {:#?}", shared_types);
let mod_name = ident(&format!("{}_mod", &target.name.to_snake_case()));

let recompile_trigger =
Self::generate_macro_recompile_trigger(target.source.path.as_ref(), no_std);

let types = generate_types(
&target.source.abi.types,
shared_types,
&target.source.abi.logged_types,
no_std,
)?;
//eprintln!("generate_bindings types {:#?}", types);

let bindings = generate_bindings(target, no_std)?;
Ok(recompile_trigger
.merge(types)
Expand Down Expand Up @@ -156,6 +170,15 @@ impl Abigen {
.filter(|ttype| ttype.is_custom_type())
}

// fn filter_alias_types(
// all_types: &[AbigenTarget],
// ) -> impl Iterator<Item = &FullTypeDeclaration> {
// all_types
// .iter()
// .flat_map(|target| &target.source.abi.types)
// .filter(|ttype| ttype.is_alias_type())
// }

/// A type is considered "shared" if it appears at least twice in
/// `all_custom_types`.
///
Expand Down
72 changes: 65 additions & 7 deletions packages/fuels-code-gen/src/program_bindings/custom_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use std::collections::{HashMap, HashSet};

use fuel_abi_types::abi::full_program::{FullLoggedType, FullTypeDeclaration};
use itertools::Itertools;
use quote::quote;
use quote::{ToTokens, quote};

use crate::{
error::Result,
program_bindings::{
custom_types::{enums::expand_custom_enum, structs::expand_custom_struct},
generated_code::GeneratedCode,
resolved_type::TypeResolver,
utils::sdk_provided_custom_types_lookup,
},
utils::TypePath,
Expand All @@ -21,7 +22,7 @@ pub(crate) mod utils;
/// Generates Rust code for each type inside `types` if:
/// * the type is not present inside `shared_types`, and
/// * if it should be generated (see: [`should_skip_codegen`], and
/// * if it is a struct or an enum.
/// * if it is a struct or an enum or an alias.
///
///
/// # Arguments
Expand All @@ -47,6 +48,8 @@ pub(crate) fn generate_types<'a>(
let log_id = log_ids.get(&ttype.type_field);
if shared_types.contains(ttype) {
reexport_the_shared_type(ttype, no_std)
} else if ttype.is_alias_type() {
expand_alias_type(ttype, no_std)
} else if ttype.is_struct_type() {
expand_custom_struct(ttype, no_std, log_id)
} else {
Expand All @@ -58,13 +61,62 @@ pub(crate) fn generate_types<'a>(
})
}

fn expand_alias_type(ttype: &FullTypeDeclaration, no_std: bool) -> Result<GeneratedCode> {
let type_path = ttype.alias_type_path().expect("This must be an alias type");
let type_path_ident = syn::Ident::new(
type_path.ident().unwrap().to_string().as_str(),
proc_macro2::Span::call_site(),
);

let alias_of = ttype.alias_of.as_ref().unwrap();
eprintln!("alias_of: {:?}", alias_of);

// let mut raw_type_str = alias_of.name.as_str();

// if let Some(stripped) = raw_type_str.strip_prefix("struct ") {
// raw_type_str = stripped;
// } else if let Some(stripped) = raw_type_str.strip_prefix("enum ") {
// raw_type_str = stripped;
// }

let resolver = TypeResolver::default();
let resolved_alias = resolver.resolve(alias_of.as_ref())?;
eprintln!("resolved_alias: {:?}", resolved_alias);
// panic!();

// let alias_of_path: syn::Type =
// syn::parse_str(raw_type_str).expect("Failed to parse type");

let alias_of_path = resolved_alias.to_token_stream();

// eprintln!("type_path: {:?}", type_path);
// panic!();

let type_mod = type_path.parent();

let top_lvl_mod = TypePath::default();
let from_current_mod_to_top_level = top_lvl_mod.relative_path_from(&type_mod);

let _path = from_current_mod_to_top_level.append(type_path);

let the_reexport = quote! {pub type #type_path_ident = #alias_of_path;};

Ok(GeneratedCode::new(the_reexport, Default::default(), no_std).wrap_in_mod(type_mod))
}

/// Instead of generating bindings for `ttype` this fn will just generate a `pub use` pointing to
/// the already generated equivalent shared type.
fn reexport_the_shared_type(ttype: &FullTypeDeclaration, no_std: bool) -> Result<GeneratedCode> {
// e.g. some_library::another_mod::SomeStruct
let type_path = ttype
.custom_type_path()
.expect("This must be a custom type due to the previous filter step");
let type_path = if ttype.is_custom_type() {
ttype
.custom_type_path()
.expect("This must be a custom type due to the previous filter step")
} else if ttype.is_alias_type() {
ttype.alias_type_path().expect("This must be an alias type")
} else {
unreachable!()
};

let type_mod = type_path.parent();

Expand Down Expand Up @@ -92,11 +144,17 @@ fn reexport_the_shared_type(ttype: &FullTypeDeclaration, no_std: bool) -> Result
// implementation details of the contract's Vec type and are not directly
// used in the SDK.
pub fn should_skip_codegen(type_decl: &FullTypeDeclaration) -> bool {
if !type_decl.is_custom_type() {
if !type_decl.is_custom_type() && !type_decl.is_alias_type() {
return true;
}

let type_path = type_decl.custom_type_path().unwrap();
let type_path = if type_decl.is_custom_type() {
type_decl.custom_type_path().unwrap()
} else if type_decl.is_alias_type() {
type_decl.alias_type_path().unwrap()
} else {
unreachable!()
};

is_type_sdk_provided(&type_path) || is_type_unused(&type_path)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use fuel_abi_types::{
abi::full_program::FullTypeDeclaration,
utils::{self, extract_generic_name},
utils::{self},
};
use proc_macro2::Ident;

Expand All @@ -11,7 +11,7 @@ pub(crate) fn extract_generic_parameters(type_decl: &FullTypeDeclaration) -> Vec
.type_parameters
.iter()
.map(|decl| {
let name = extract_generic_name(&decl.type_field).unwrap_or_else(|| {
let name = decl.generic_name().unwrap_or_else(|| {
panic!("Type parameters should only contain ids of generic types!")
});
utils::ident(&name)
Expand Down
Loading
Loading