fuels_code_gen/program_bindings/
utils.rs1use std::collections::{HashMap, HashSet};
2
3use fuel_abi_types::abi::full_program::FullTypeApplication;
4use inflector::Inflector;
5use itertools::Itertools;
6use proc_macro2::{Ident, TokenStream};
7use quote::quote;
8
9use crate::{
10 error::Result,
11 program_bindings::resolved_type::{GenericType, ResolvedType, TypeResolver},
12 utils::{self, TypePath, safe_ident},
13};
14
15#[derive(Debug)]
16pub(crate) struct Component {
17 pub(crate) ident: Ident,
18 pub(crate) resolved_type: ResolvedType,
19 pub(crate) error_message: Option<String>,
20}
21
22#[derive(Debug)]
23pub(crate) struct Components {
24 components: Vec<Component>,
25}
26
27impl Components {
28 pub fn new(
29 type_applications: &[FullTypeApplication],
30 snake_case: bool,
31 parent_module: TypePath,
32 ) -> Result<Self> {
33 let type_resolver = TypeResolver::new(parent_module);
34 let components = type_applications
35 .iter()
36 .map(|type_application| {
37 let name = if snake_case {
38 type_application.name.to_snake_case()
39 } else {
40 type_application.name.to_owned()
41 };
42
43 let ident = safe_ident(&name);
44 let resolved_type = type_resolver.resolve(type_application)?;
45 let error_message = type_application.error_message.clone();
46
47 Result::Ok(Component {
48 ident,
49 resolved_type,
50 error_message,
51 })
52 })
53 .collect::<Result<Vec<_>>>()?;
54
55 Ok(Self { components })
56 }
57
58 pub fn has_error_messages(&self) -> bool {
59 self.components
60 .iter()
61 .all(|component| component.error_message.is_some())
62 }
63
64 pub fn iter(&self) -> impl Iterator<Item = &Component> {
65 self.components.iter()
66 }
67
68 pub fn is_empty(&self) -> bool {
69 self.components.is_empty()
70 }
71
72 pub fn as_enum_variants(&self) -> impl Iterator<Item = TokenStream> + '_ {
73 self.components.iter().map(
74 |Component {
75 ident,
76 resolved_type,
77 ..
78 }| {
79 if let ResolvedType::Unit = resolved_type {
80 quote! {#ident}
81 } else {
82 quote! {#ident(#resolved_type)}
83 }
84 },
85 )
86 }
87
88 pub fn generate_parameters_for_unused_generics(
89 &self,
90 declared_generics: &[Ident],
91 ) -> (Vec<Ident>, Vec<TokenStream>) {
92 self.unused_named_generics(declared_generics)
93 .enumerate()
94 .map(|(index, generic)| {
95 let ident = utils::ident(&format!("_unused_generic_{index}"));
96 let ty = quote! {::core::marker::PhantomData<#generic>};
97 (ident, ty)
98 })
99 .unzip()
100 }
101
102 pub fn generate_variant_for_unused_generics(
103 &self,
104 declared_generics: &[Ident],
105 ) -> Option<TokenStream> {
106 let phantom_types = self
107 .unused_named_generics(declared_generics)
108 .map(|generic| {
109 quote! {::core::marker::PhantomData<#generic>}
110 })
111 .collect_vec();
112
113 (!phantom_types.is_empty()).then(|| {
114 quote! {
115 #[Ignore]
116 IgnoreMe(#(#phantom_types),*)
117 }
118 })
119 }
120
121 fn named_generics(&self) -> HashSet<Ident> {
122 self.components
123 .iter()
124 .flat_map(|Component { resolved_type, .. }| resolved_type.generics())
125 .filter_map(|generic_type| {
126 if let GenericType::Named(name) = generic_type {
127 Some(name)
128 } else {
129 None
130 }
131 })
132 .collect()
133 }
134
135 fn unused_named_generics<'a>(
136 &'a self,
137 declared_generics: &'a [Ident],
138 ) -> impl Iterator<Item = &'a Ident> {
139 let used_generics = self.named_generics();
140 declared_generics
141 .iter()
142 .filter(move |generic| !used_generics.contains(generic))
143 }
144}
145
146pub(crate) fn tokenize_generics(generics: &[Ident]) -> (TokenStream, TokenStream) {
147 if generics.is_empty() {
148 return (Default::default(), Default::default());
149 }
150
151 (
152 quote! {<#(#generics,)*>},
153 quote! {<#(#generics: ::fuels::core::traits::Tokenizable + ::fuels::core::traits::Parameterize, )*>},
154 )
155}
156
157pub(crate) fn sdk_provided_custom_types_lookup() -> HashMap<TypePath, TypePath> {
158 [
159 ("std::address::Address", "::fuels::types::Address"),
160 ("std::asset_id::AssetId", "::fuels::types::AssetId"),
161 ("std::b512::B512", "::fuels::types::B512"),
162 ("std::bytes::Bytes", "::fuels::types::Bytes"),
163 ("std::contract_id::ContractId", "::fuels::types::ContractId"),
164 ("std::identity::Identity", "::fuels::types::Identity"),
165 ("std::option::Option", "::core::option::Option"),
166 ("std::result::Result", "::core::result::Result"),
167 ("std::string::String", "::std::string::String"),
168 ("std::vec::Vec", "::std::vec::Vec"),
169 (
170 "std::vm::evm::evm_address::EvmAddress",
171 "::fuels::types::EvmAddress",
172 ),
173 ]
174 .into_iter()
175 .map(|(original_type_path, provided_type_path)| {
176 let msg = "known at compile time to be correctly formed";
177 (
178 TypePath::new(original_type_path).expect(msg),
179 TypePath::new(provided_type_path).expect(msg),
180 )
181 })
182 .collect()
183}
184
185#[cfg(test)]
186mod tests {
187 use fuel_abi_types::abi::full_program::FullTypeDeclaration;
188
189 use super::*;
190
191 #[test]
192 fn respects_snake_case_flag() -> Result<()> {
193 let type_application = type_application_named("WasNotSnakeCased");
195
196 let sut = Components::new(&[type_application], true, TypePath::default())?;
198
199 assert_eq!(sut.iter().next().unwrap().ident, "was_not_snake_cased");
201
202 Ok(())
203 }
204
205 #[test]
206 fn avoids_collisions_with_reserved_keywords() -> Result<()> {
207 {
208 let type_application = type_application_named("if");
209
210 let sut = Components::new(&[type_application], false, TypePath::default())?;
211
212 assert_eq!(sut.iter().next().unwrap().ident, "if_");
213 }
214
215 {
216 let type_application = type_application_named("let");
217
218 let sut = Components::new(&[type_application], false, TypePath::default())?;
219
220 assert_eq!(sut.iter().next().unwrap().ident, "let_");
221 }
222
223 Ok(())
224 }
225
226 fn type_application_named(name: &str) -> FullTypeApplication {
227 FullTypeApplication {
228 name: name.to_string(),
229 type_decl: FullTypeDeclaration {
230 type_field: "u64".to_string(),
231 components: vec![],
232 type_parameters: vec![],
233 alias_of: None,
234 },
235 type_arguments: vec![],
236 error_message: None,
237 }
238 }
239}