Skip to content

Commit 2042862

Browse files
committed
Add support for ABI alias types
1 parent 865e00c commit 2042862

File tree

16 files changed

+298
-18
lines changed

16 files changed

+298
-18
lines changed

Cargo.toml

Lines changed: 2 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,7 @@ 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.15.3"
56+
fuel-abi-types = "0.16.0"
5657
futures = "0.3.29"
5758
hex = { version = "0.4.3", default-features = false }
5859
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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
contract;
2+
3+
pub type MyAlias = Vec<b256>;
4+
pub type MyU64 = u64;
5+
pub type MyTuple = (MyU64, MyU64);
6+
pub type MyArray = [MyTuple; 2];
7+
8+
abi MyContract {
9+
fn with_b256(b: b256) -> b256;
10+
fn with_myalias_vec() -> MyAlias;
11+
fn with_mytuple() -> MyTuple;
12+
}
13+
14+
impl MyContract for Contract {
15+
fn with_b256(b: b256) -> b256 {
16+
b256::zero()
17+
}
18+
19+
fn with_myalias_vec() -> MyAlias {
20+
MyAlias::new()
21+
}
22+
23+
fn with_mytuple() -> MyTuple {
24+
(32, 64)
25+
}
26+
}

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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
contract;
2+
3+
pub type MyAlias = b256;
4+
pub type MyU64 = u64;
5+
pub type MyTuple = (MyU64, MyU64);
6+
7+
abi MyContract {
8+
fn with_myalias(b: MyAlias) -> MyAlias;
9+
fn with_mytuple() -> MyTuple;
10+
}
11+
12+
impl MyContract for Contract {
13+
fn with_myalias(b: MyAlias) -> MyAlias {
14+
b256::zero()
15+
}
16+
17+
fn with_mytuple() -> MyTuple {
18+
(32, 64)
19+
}
20+
}

e2e/tests/alias.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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
23+
.with_myalias(MyAlias::zeroed())
24+
.call()
25+
.await?;
26+
27+
assert_eq!(response.value, MyAlias::zeroed());
28+
29+
Ok(())
30+
}

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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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(
12+
"/home/joao/dev/fuels-rs/e2e/sway/contracts/alias/out/release/alias-abi.json",
13+
)
14+
// Abi::load_from("/home/joao/dev/sway/_test_aliases_abi/out/debug/test-case-abi.json")
15+
.unwrap(),
16+
ProgramType::Contract,
17+
);
18+
let targets = vec![target];
19+
20+
let _abigen = Abigen::generate(targets, false).expect("abigen generation failed");
21+
}
22+
}

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

Lines changed: 23 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 {
@@ -73,9 +80,12 @@ impl Abigen {
7380
.iter()
7481
.flat_map(|abi| abi.source.abi.logged_types.clone())
7582
.collect_vec();
83+
7684
let bindings = Self::generate_all_bindings(parsed_targets, no_std, &shared_types)?;
85+
// eprintln!("bindings {:#}", bindings.code().to_string());
7786

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

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

104115
let recompile_trigger =
105116
Self::generate_macro_recompile_trigger(target.source.path.as_ref(), no_std);
117+
106118
let types = generate_types(
107119
&target.source.abi.types,
108120
shared_types,
109121
&target.source.abi.logged_types,
110122
no_std,
111123
)?;
124+
//eprintln!("generate_bindings types {:#?}", types);
125+
112126
let bindings = generate_bindings(target, no_std)?;
113127
Ok(recompile_trigger
114128
.merge(types)
@@ -156,6 +170,15 @@ impl Abigen {
156170
.filter(|ttype| ttype.is_custom_type())
157171
}
158172

173+
// fn filter_alias_types(
174+
// all_types: &[AbigenTarget],
175+
// ) -> impl Iterator<Item = &FullTypeDeclaration> {
176+
// all_types
177+
// .iter()
178+
// .flat_map(|target| &target.source.abi.types)
179+
// .filter(|ttype| ttype.is_alias_type())
180+
// }
181+
159182
/// A type is considered "shared" if it appears at least twice in
160183
/// `all_custom_types`.
161184
///

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

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ use std::collections::{HashMap, HashSet};
22

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

77
use crate::{
88
error::Result,
99
program_bindings::{
1010
custom_types::{enums::expand_custom_enum, structs::expand_custom_struct},
1111
generated_code::GeneratedCode,
12+
resolved_type::TypeResolver,
1213
utils::sdk_provided_custom_types_lookup,
1314
},
1415
utils::TypePath,
@@ -21,7 +22,7 @@ pub(crate) mod utils;
2122
/// Generates Rust code for each type inside `types` if:
2223
/// * the type is not present inside `shared_types`, and
2324
/// * if it should be generated (see: [`should_skip_codegen`], and
24-
/// * if it is a struct or an enum.
25+
/// * if it is a struct or an enum or an alias.
2526
///
2627
///
2728
/// # Arguments
@@ -47,6 +48,8 @@ pub(crate) fn generate_types<'a>(
4748
let log_id = log_ids.get(&ttype.type_field);
4849
if shared_types.contains(ttype) {
4950
reexport_the_shared_type(ttype, no_std)
51+
} else if ttype.is_alias_type() {
52+
expand_alias_type(ttype, no_std)
5053
} else if ttype.is_struct_type() {
5154
expand_custom_struct(ttype, no_std, log_id)
5255
} else {
@@ -58,13 +61,62 @@ pub(crate) fn generate_types<'a>(
5861
})
5962
}
6063

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

69121
let type_mod = type_path.parent();
70122

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

99-
let type_path = type_decl.custom_type_path().unwrap();
151+
let type_path = if type_decl.is_custom_type() {
152+
type_decl.custom_type_path().unwrap()
153+
} else if type_decl.is_alias_type() {
154+
type_decl.alias_type_path().unwrap()
155+
} else {
156+
unreachable!()
157+
};
100158

101159
is_type_sdk_provided(&type_path) || is_type_unused(&type_path)
102160
}

0 commit comments

Comments
 (0)