fuels_code_gen/program_bindings/
abigen.rs1use std::{collections::HashSet, path::PathBuf};
2
3pub use abigen_target::{Abi, AbigenTarget, ProgramType};
4use fuel_abi_types::abi::full_program::{FullLoggedType, FullTypeDeclaration};
5use inflector::Inflector;
6use itertools::Itertools;
7use proc_macro2::TokenStream;
8use quote::quote;
9use regex::Regex;
10
11use crate::{
12 error::Result,
13 program_bindings::{
14 abigen::bindings::generate_bindings, custom_types::generate_types,
15 generated_code::GeneratedCode,
16 },
17 utils::ident,
18};
19
20mod abigen_target;
21mod bindings;
22mod configurables;
23mod logs;
24
25pub struct Abigen;
26
27impl Abigen {
28 pub fn generate(targets: Vec<AbigenTarget>, no_std: bool) -> Result<TokenStream> {
37 let generated_code = Self::generate_code(no_std, targets)?;
38
39 let use_statements = generated_code.use_statements_for_uniquely_named_types();
40
41 let code = if no_std {
42 Self::wasm_paths_hotfix(&generated_code.code())
43 } else {
44 generated_code.code()
45 };
46
47 Ok(quote! {
48 #code
49 #use_statements
50 })
51 }
52 fn wasm_paths_hotfix(code: &TokenStream) -> TokenStream {
53 [
54 (r"::\s*std\s*::\s*string", "::alloc::string"),
55 (r"::\s*std\s*::\s*format", "::alloc::format"),
56 (r"::\s*std\s*::\s*vec", "::alloc::vec"),
57 (r"::\s*std\s*::\s*boxed", "::alloc::boxed"),
58 ]
59 .map(|(reg_expr_str, substitute)| (Regex::new(reg_expr_str).unwrap(), substitute))
60 .into_iter()
61 .fold(code.to_string(), |code, (regex, wasm_include)| {
62 regex.replace_all(&code, wasm_include).to_string()
63 })
64 .parse()
65 .expect("Wasm hotfix failed!")
66 }
67
68 fn generate_code(no_std: bool, parsed_targets: Vec<AbigenTarget>) -> Result<GeneratedCode> {
69 let custom_types = Self::filter_custom_types(&parsed_targets);
70 let shared_types = Self::filter_shared_types(custom_types);
71
72 let logged_types = parsed_targets
73 .iter()
74 .flat_map(|abi| abi.source.abi.logged_types.clone())
75 .collect_vec();
76 let bindings = Self::generate_all_bindings(parsed_targets, no_std, &shared_types)?;
77
78 let shared_types = Self::generate_shared_types(shared_types, &logged_types, no_std)?;
79
80 let mod_name = ident("abigen_bindings");
81 Ok(shared_types.merge(bindings).wrap_in_mod(mod_name))
82 }
83
84 fn generate_all_bindings(
85 targets: Vec<AbigenTarget>,
86 no_std: bool,
87 shared_types: &HashSet<FullTypeDeclaration>,
88 ) -> Result<GeneratedCode> {
89 targets
90 .into_iter()
91 .map(|target| Self::generate_binding(target, no_std, shared_types))
92 .fold_ok(GeneratedCode::default(), |acc, generated_code| {
93 acc.merge(generated_code)
94 })
95 }
96
97 fn generate_binding(
98 target: AbigenTarget,
99 no_std: bool,
100 shared_types: &HashSet<FullTypeDeclaration>,
101 ) -> Result<GeneratedCode> {
102 let mod_name = ident(&format!("{}_mod", &target.name.to_snake_case()));
103
104 let recompile_trigger =
105 Self::generate_macro_recompile_trigger(target.source.path.as_ref(), no_std);
106 let types = generate_types(
107 &target.source.abi.types,
108 shared_types,
109 &target.source.abi.logged_types,
110 no_std,
111 )?;
112 let bindings = generate_bindings(target, no_std)?;
113 Ok(recompile_trigger
114 .merge(types)
115 .merge(bindings)
116 .wrap_in_mod(mod_name))
117 }
118
119 fn generate_macro_recompile_trigger(path: Option<&PathBuf>, no_std: bool) -> GeneratedCode {
123 let code = path
124 .as_ref()
125 .map(|path| {
126 let stringified_path = path.display().to_string();
127 quote! {
128 const _: &[u8] = include_bytes!(#stringified_path);
129 }
130 })
131 .unwrap_or_default();
132 GeneratedCode::new(code, Default::default(), no_std)
133 }
134
135 fn generate_shared_types(
136 shared_types: HashSet<FullTypeDeclaration>,
137 logged_types: &Vec<FullLoggedType>,
138 no_std: bool,
139 ) -> Result<GeneratedCode> {
140 let types = generate_types(&shared_types, &HashSet::default(), logged_types, no_std)?;
141
142 if types.is_empty() {
143 Ok(Default::default())
144 } else {
145 let mod_name = ident("shared_types");
146 Ok(types.wrap_in_mod(mod_name))
147 }
148 }
149
150 fn filter_custom_types(
151 all_types: &[AbigenTarget],
152 ) -> impl Iterator<Item = &FullTypeDeclaration> {
153 all_types
154 .iter()
155 .flat_map(|target| &target.source.abi.types)
156 .filter(|ttype| ttype.is_custom_type())
157 }
158
159 fn filter_shared_types<'a>(
167 all_custom_types: impl IntoIterator<Item = &'a FullTypeDeclaration>,
168 ) -> HashSet<FullTypeDeclaration> {
169 all_custom_types.into_iter().duplicates().cloned().collect()
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn correctly_determines_shared_types() {
179 let types = ["type_0", "type_1", "type_0"].map(|type_field| FullTypeDeclaration {
180 type_field: type_field.to_string(),
181 components: vec![],
182 type_parameters: vec![],
183 alias_of: None,
184 });
185
186 let shared_types = Abigen::filter_shared_types(&types);
187
188 assert_eq!(shared_types, HashSet::from([types[0].clone()]))
189 }
190}