Skip to content

Commit bd37700

Browse files
committed
Add support for ABI alias types
1 parent aed2d97 commit bd37700

File tree

14 files changed

+268
-12
lines changed

14 files changed

+268
-12
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ resolver = "2"
88
members = [
99
"e2e",
1010
"examples/codec",
11+
"examples/codegen",
1112
"examples/contracts",
1213
"examples/cookbook",
1314
"examples/debugging",
@@ -52,7 +53,8 @@ cynic = { version = "3.1.0", default-features = false }
5253
test-case = { version = "3.3", default-features = false }
5354
eth-keystore = "0.5.0"
5455
flate2 = { version = "1.0", default-features = false }
55-
fuel-abi-types = "0.13.0"
56+
#fuel-abi-types = "0.13.0"
57+
fuel-abi-types = { path = "../fuel-abi-types", version = "0.13.0" }
5658
futures = "0.3.29"
5759
hex = { version = "0.4.3", default-features = false }
5860
itertools = "0.12.0"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[project]
2+
authors = ["Fuel Labs <[email protected]>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "contract_with_alias"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
contract;
2+
3+
pub type MyAlias = Vec<b256>;
4+
//pub type MyAliasGeneric<T> = Vec<T>;
5+
6+
abi MyContract {
7+
fn with_b256(b: b256) -> b256;
8+
fn with_myalias_vec() -> MyAlias;
9+
}
10+
11+
impl MyContract for Contract {
12+
fn with_b256(b: b256) -> b256 {
13+
b256::zero()
14+
}
15+
16+
fn with_myalias_vec() -> MyAlias {
17+
MyAlias::new()
18+
}
19+
}

e2e/sway/contracts/alias/Forc.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[project]
2+
authors = ["Fuel Labs <[email protected]>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "alias"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
contract;
2+
3+
pub type MyAlias = b256;
4+
5+
abi MyContract {
6+
fn with_myalias(b: MyAlias) -> MyAlias;
7+
}
8+
9+
impl MyContract for Contract {
10+
fn with_myalias(b: MyAlias) -> MyAlias {
11+
b256::zero()
12+
}
13+
}

e2e/tests/alias.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use fuels::prelude::*;
2+
3+
#[tokio::test]
4+
async fn test_alias() -> Result<()> {
5+
setup_program_test!(
6+
Wallets("wallet"),
7+
Abigen(Contract(
8+
name = "MyContract",
9+
project = "e2e/sway/contracts/alias"
10+
)),
11+
Deploy(
12+
name = "contract_instance",
13+
contract = "MyContract",
14+
wallet = "wallet",
15+
random_salt = false,
16+
),
17+
);
18+
19+
// Make sure we can call the contract with multiple arguments
20+
let contract_methods = contract_instance.methods();
21+
use abigen_bindings::my_contract_mod::MyAlias;
22+
let response = contract_methods.with_myalias(MyAlias::zeroed()).call().await?;
23+
24+
assert_eq!(response.value, MyAlias::zeroed());
25+
26+
Ok(())
27+
}

examples/codegen/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "fuels-example-codegen"
3+
version = { workspace = true }
4+
authors = { workspace = true }
5+
edition = { workspace = true }
6+
homepage = { workspace = true }
7+
license = { workspace = true }
8+
publish = false
9+
repository = { workspace = true }
10+
description = "Fuel Rust SDK codegen examples."
11+
12+
[dev-dependencies]
13+
fuels = { workspace = true, features = ["default"] }
14+
fuels-code-gen = { workspace = true }
15+
tokio = { workspace = true, features = ["full"] }

examples/codegen/src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
extern crate alloc;
2+
3+
#[cfg(test)]
4+
mod tests {
5+
#[test]
6+
fn example_alias() {
7+
use fuels::code_gen::*;
8+
9+
let target = AbigenTarget::new(
10+
"MyContract".into(),
11+
Abi::load_from("/home/joao/dev/fuels-rs/e2e/sway/abi/contract_with_alias_0/out/release/contract_with_alias-abi.json")
12+
//Abi::load_from("/home/joao/dev/fuels-rs/e2e/sway/abi/contract_with_alias/out/release/contract_with_alias-abi.json")
13+
// Abi::load_from("/home/joao/dev/sway/_test_aliases_abi/out/debug/test-case-abi.json")
14+
.unwrap(),
15+
ProgramType::Contract,
16+
);
17+
let targets = vec![target];
18+
19+
let abigen = Abigen::generate(targets, false).expect("abigen generation failed");
20+
}
21+
}

packages/fuels-code-gen/src/program_bindings/abigen.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,15 @@ impl Abigen {
3434
/// for, and of what nature (Contract, Script or Predicate).
3535
/// * `no_std`: don't use the Rust std library.
3636
pub fn generate(targets: Vec<AbigenTarget>, no_std: bool) -> Result<TokenStream> {
37+
// eprintln!("{:#?}", targets);
38+
3739
let generated_code = Self::generate_code(no_std, targets)?;
3840

41+
// eprintln!(
42+
// "========================== CODE: \n {:#}",
43+
// generated_code.code()
44+
// );
45+
3946
let use_statements = generated_code.use_statements_for_uniquely_named_types();
4047

4148
let code = if no_std {
@@ -69,8 +76,15 @@ impl Abigen {
6976
let custom_types = Self::filter_custom_types(&parsed_targets);
7077
let shared_types = Self::filter_shared_types(custom_types);
7178

79+
// TODO: Shared type handling.
80+
let _alias_types = Self::filter_alias_types(&parsed_targets)
81+
.cloned()
82+
.collect::<Vec<_>>();
83+
7284
let bindings = Self::generate_all_bindings(parsed_targets, no_std, &shared_types)?;
85+
//eprintln!("bindings {:#}", bindings.code().to_string());
7386
let shared_types = Self::generate_shared_types(shared_types, no_std)?;
87+
//eprintln!("shared_types {:#}", shared_types.code().to_string());
7488

7589
let mod_name = ident("abigen_bindings");
7690
Ok(shared_types.merge(bindings).wrap_in_mod(mod_name))
@@ -94,11 +108,14 @@ impl Abigen {
94108
no_std: bool,
95109
shared_types: &HashSet<FullTypeDeclaration>,
96110
) -> Result<GeneratedCode> {
111+
//eprintln!("generate_bindings shared_types {:#?}", shared_types);
97112
let mod_name = ident(&format!("{}_mod", &target.name.to_snake_case()));
98113

99114
let recompile_trigger =
100115
Self::generate_macro_recompile_trigger(target.source.path.as_ref(), no_std);
101116
let types = generate_types(&target.source.abi.types, shared_types, no_std)?;
117+
//eprintln!("generate_bindings types {:#?}", types);
118+
102119
let bindings = generate_bindings(target, no_std)?;
103120
Ok(recompile_trigger
104121
.merge(types)
@@ -145,6 +162,15 @@ impl Abigen {
145162
.filter(|ttype| ttype.is_custom_type())
146163
}
147164

165+
fn filter_alias_types(
166+
all_types: &[AbigenTarget],
167+
) -> impl Iterator<Item = &FullTypeDeclaration> {
168+
all_types
169+
.iter()
170+
.flat_map(|target| &target.source.abi.types)
171+
.filter(|ttype| ttype.is_alias_type())
172+
}
173+
148174
/// A type is considered "shared" if it appears at least twice in
149175
/// `all_custom_types`.
150176
///

packages/fuels-code-gen/src/program_bindings/custom_types.rs

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ use std::collections::HashSet;
22

33
use fuel_abi_types::abi::full_program::FullTypeDeclaration;
44
use itertools::Itertools;
5-
use quote::quote;
5+
use quote::{quote, ToTokens};
66

77
use crate::{
88
error::Result,
99
program_bindings::{
10-
custom_types::{enums::expand_custom_enum, structs::expand_custom_struct},
11-
generated_code::GeneratedCode,
12-
utils::sdk_provided_custom_types_lookup,
10+
custom_types::{enums::expand_custom_enum, structs::expand_custom_struct}, generated_code::GeneratedCode, resolved_type::TypeResolver, utils::sdk_provided_custom_types_lookup
1311
},
1412
utils::TypePath,
1513
};
@@ -40,6 +38,8 @@ pub(crate) fn generate_types<'a, T: IntoIterator<Item = &'a FullTypeDeclaration>
4038
.map(|ttype: &FullTypeDeclaration| {
4139
if shared_types.contains(ttype) {
4240
reexport_the_shared_type(ttype, no_std)
41+
} else if ttype.is_alias_type() {
42+
expand_alias_type(ttype, no_std)
4343
} else if ttype.is_struct_type() {
4444
expand_custom_struct(ttype, no_std)
4545
} else {
@@ -51,13 +51,62 @@ pub(crate) fn generate_types<'a, T: IntoIterator<Item = &'a FullTypeDeclaration>
5151
})
5252
}
5353

54+
fn expand_alias_type(ttype: &FullTypeDeclaration, no_std: bool) -> Result<GeneratedCode> {
55+
let type_path = ttype.alias_type_path().expect("This must be an alias type");
56+
let type_path_ident = syn::Ident::new(
57+
type_path.ident().unwrap().to_string().as_str(),
58+
proc_macro2::Span::call_site(),
59+
);
60+
61+
let alias_of = ttype.alias_of.as_ref().unwrap();
62+
//eprintln!("alias_of: {:?}", alias_of);
63+
64+
// let mut raw_type_str = alias_of.name.as_str();
65+
66+
// if let Some(stripped) = raw_type_str.strip_prefix("struct ") {
67+
// raw_type_str = stripped;
68+
// } else if let Some(stripped) = raw_type_str.strip_prefix("enum ") {
69+
// raw_type_str = stripped;
70+
// }
71+
72+
let resolver = TypeResolver::default();
73+
let resolved_alias = resolver.resolve(alias_of)?;
74+
//eprintln!("resolved_alias: {:?}", resolved_alias);
75+
// panic!();
76+
77+
// let alias_of_path: syn::Type =
78+
// syn::parse_str(raw_type_str).expect("Failed to parse type");
79+
80+
let alias_of_path = resolved_alias.to_token_stream();
81+
82+
// eprintln!("type_path: {:?}", type_path);
83+
// panic!();
84+
85+
let type_mod = type_path.parent();
86+
87+
let top_lvl_mod = TypePath::default();
88+
let from_current_mod_to_top_level = top_lvl_mod.relative_path_from(&type_mod);
89+
90+
let path = from_current_mod_to_top_level.append(type_path);
91+
92+
let the_reexport = quote! {pub type #type_path_ident = #alias_of_path;};
93+
94+
Ok(GeneratedCode::new(the_reexport, Default::default(), no_std).wrap_in_mod(type_mod))
95+
}
96+
5497
/// Instead of generating bindings for `ttype` this fn will just generate a `pub use` pointing to
5598
/// the already generated equivalent shared type.
5699
fn reexport_the_shared_type(ttype: &FullTypeDeclaration, no_std: bool) -> Result<GeneratedCode> {
57100
// e.g. some_library::another_mod::SomeStruct
58-
let type_path = ttype
59-
.custom_type_path()
60-
.expect("This must be a custom type due to the previous filter step");
101+
let type_path = if ttype.is_custom_type() {
102+
ttype
103+
.custom_type_path()
104+
.expect("This must be a custom type due to the previous filter step")
105+
} else if ttype.is_alias_type() {
106+
ttype.alias_type_path().expect("This must be an alias type")
107+
} else {
108+
unreachable!()
109+
};
61110

62111
let type_mod = type_path.parent();
63112

@@ -85,6 +134,10 @@ fn reexport_the_shared_type(ttype: &FullTypeDeclaration, no_std: bool) -> Result
85134
// implementation details of the contract's Vec type and are not directly
86135
// used in the SDK.
87136
fn should_skip_codegen(type_decl: &FullTypeDeclaration) -> bool {
137+
if type_decl.is_alias_type() {
138+
return false;
139+
}
140+
88141
if !type_decl.is_custom_type() {
89142
return true;
90143
}

0 commit comments

Comments
 (0)