fuels_code_gen/program_bindings/
utils.rs

1use 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        // given
194        let type_application = type_application_named("WasNotSnakeCased");
195
196        // when
197        let sut = Components::new(&[type_application], true, TypePath::default())?;
198
199        // then
200        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}