fuels_code_gen/program_bindings/
resolved_type.rs

1use std::fmt::{Display, Formatter};
2
3use fuel_abi_types::{
4    abi::full_program::FullTypeApplication,
5    utils::{self, extract_array_len, extract_generic_name, extract_str_len, has_tuple_format},
6};
7use proc_macro2::{Ident, TokenStream};
8use quote::{ToTokens, quote};
9
10use crate::{
11    error::{Result, error},
12    program_bindings::utils::sdk_provided_custom_types_lookup,
13    utils::TypePath,
14};
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum GenericType {
18    Named(Ident),
19    Constant(usize),
20}
21
22impl ToTokens for GenericType {
23    fn to_tokens(&self, tokens: &mut TokenStream) {
24        let stream = match self {
25            GenericType::Named(ident) => ident.to_token_stream(),
26            GenericType::Constant(constant) => constant.to_token_stream(),
27        };
28
29        tokens.extend(stream);
30    }
31}
32
33/// Represents a Rust type alongside its generic parameters. For when you want to reference an ABI
34/// type in Rust code since [`ResolvedType`] can be converted into a [`TokenStream`] via
35/// `resolved_type.to_token_stream()`.
36#[derive(Debug, Clone)]
37pub enum ResolvedType {
38    Unit,
39    Primitive(TypePath),
40    StructOrEnum {
41        path: TypePath,
42        generics: Vec<ResolvedType>,
43    },
44    Array(Box<ResolvedType>, usize),
45    Tuple(Vec<ResolvedType>),
46    Generic(GenericType),
47}
48
49impl ResolvedType {
50    pub fn generics(&self) -> Vec<GenericType> {
51        match self {
52            ResolvedType::StructOrEnum {
53                generics: elements, ..
54            }
55            | ResolvedType::Tuple(elements) => {
56                elements.iter().flat_map(|el| el.generics()).collect()
57            }
58            ResolvedType::Array(el, _) => el.generics(),
59            ResolvedType::Generic(inner) => vec![inner.clone()],
60            _ => vec![],
61        }
62    }
63}
64
65impl ToTokens for ResolvedType {
66    fn to_tokens(&self, tokens: &mut TokenStream) {
67        let tokenized = match self {
68            ResolvedType::Unit => quote! {()},
69            ResolvedType::Primitive(path) => path.into_token_stream(),
70            ResolvedType::StructOrEnum { path, generics } => {
71                if generics.is_empty() {
72                    path.to_token_stream()
73                } else {
74                    quote! { #path<#(#generics),*>}
75                }
76            }
77            ResolvedType::Array(el, count) => quote! { [#el; #count]},
78            ResolvedType::Tuple(elements) => {
79                // it is important to leave a trailing comma because a tuple with
80                // one element is written as (element,) not (element) which is
81                // resolved to just element
82                quote! { (#(#elements,)*) }
83            }
84            ResolvedType::Generic(generic_type) => generic_type.into_token_stream(),
85        };
86
87        tokens.extend(tokenized)
88    }
89}
90
91impl Display for ResolvedType {
92    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93        write!(f, "{}", self.to_token_stream())
94    }
95}
96
97/// Used to resolve [`FullTypeApplication`]s into [`ResolvedType`]s
98pub(crate) struct TypeResolver {
99    /// The mod in which the produced [`ResolvedType`]s are going to end up in.
100    current_mod: TypePath,
101}
102
103impl Default for TypeResolver {
104    fn default() -> Self {
105        TypeResolver::new(Default::default())
106    }
107}
108
109impl TypeResolver {
110    pub(crate) fn new(current_mod: TypePath) -> Self {
111        Self { current_mod }
112    }
113
114    pub(crate) fn resolve(&self, type_application: &FullTypeApplication) -> Result<ResolvedType> {
115        let resolvers = [
116            Self::try_as_primitive_type,
117            Self::try_as_bits256,
118            Self::try_as_generic,
119            Self::try_as_array,
120            Self::try_as_sized_ascii_string,
121            Self::try_as_ascii_string,
122            Self::try_as_tuple,
123            Self::try_as_raw_slice,
124            Self::try_as_custom_type,
125        ];
126
127        for resolver in resolvers {
128            if let Some(resolved) = resolver(self, type_application)? {
129                return Ok(resolved);
130            }
131        }
132
133        let type_field = &type_application.type_decl.type_field;
134        Err(error!("could not resolve '{type_field}' to any known type"))
135    }
136
137    fn resolve_multiple(
138        &self,
139        type_applications: &[FullTypeApplication],
140    ) -> Result<Vec<ResolvedType>> {
141        type_applications
142            .iter()
143            .map(|type_application| self.resolve(type_application))
144            .collect()
145    }
146
147    fn try_as_generic(
148        &self,
149        type_application: &FullTypeApplication,
150    ) -> Result<Option<ResolvedType>> {
151        let Some(name) = extract_generic_name(&type_application.type_decl.type_field) else {
152            return Ok(None);
153        };
154
155        let ident = utils::safe_ident(&name);
156        Ok(Some(ResolvedType::Generic(GenericType::Named(ident))))
157    }
158
159    fn try_as_array(&self, type_application: &FullTypeApplication) -> Result<Option<ResolvedType>> {
160        let type_decl = &type_application.type_decl;
161        let Some(len) = extract_array_len(&type_decl.type_field) else {
162            return Ok(None);
163        };
164
165        let components = self.resolve_multiple(&type_decl.components)?;
166        let type_inside = match components.as_slice() {
167            [single_type] => single_type,
168            other => {
169                return Err(error!(
170                    "array must have only one component. Actual components: {other:?}"
171                ));
172            }
173        };
174
175        Ok(Some(ResolvedType::Array(
176            Box::new(type_inside.clone()),
177            len,
178        )))
179    }
180
181    fn try_as_sized_ascii_string(
182        &self,
183        type_application: &FullTypeApplication,
184    ) -> Result<Option<ResolvedType>> {
185        let Some(len) = extract_str_len(&type_application.type_decl.type_field) else {
186            return Ok(None);
187        };
188
189        let path =
190            TypePath::new("::fuels::types::SizedAsciiString").expect("this is a valid TypePath");
191        Ok(Some(ResolvedType::StructOrEnum {
192            path,
193            generics: vec![ResolvedType::Generic(GenericType::Constant(len))],
194        }))
195    }
196
197    fn try_as_ascii_string(
198        &self,
199        type_application: &FullTypeApplication,
200    ) -> Result<Option<ResolvedType>> {
201        let maybe_resolved = (type_application.type_decl.type_field == "str").then(|| {
202            let path =
203                TypePath::new("::fuels::types::AsciiString").expect("this is a valid TypePath");
204            ResolvedType::StructOrEnum {
205                path,
206                generics: vec![],
207            }
208        });
209
210        Ok(maybe_resolved)
211    }
212
213    fn try_as_tuple(&self, type_application: &FullTypeApplication) -> Result<Option<ResolvedType>> {
214        let type_decl = &type_application.type_decl;
215        if !has_tuple_format(&type_decl.type_field) {
216            return Ok(None);
217        }
218        let inner_types = self.resolve_multiple(&type_decl.components)?;
219
220        Ok(Some(ResolvedType::Tuple(inner_types)))
221    }
222
223    fn try_as_primitive_type(
224        &self,
225        type_decl: &FullTypeApplication,
226    ) -> Result<Option<ResolvedType>> {
227        let type_field = &type_decl.type_decl.type_field;
228
229        let maybe_resolved = match type_field.as_str() {
230            "()" => Some(ResolvedType::Unit),
231            "bool" | "u8" | "u16" | "u32" | "u64" => {
232                let path = format!("::core::primitive::{type_field}");
233                let type_path = TypePath::new(path).expect("to be a valid path");
234
235                Some(ResolvedType::Primitive(type_path))
236            }
237            "struct std::u128::U128" | "struct U128" => {
238                let u128_path = TypePath::new("::core::primitive::u128").expect("is correct");
239                Some(ResolvedType::Primitive(u128_path))
240            }
241            "u256" => {
242                let u256_path = TypePath::new("::fuels::types::U256").expect("is correct");
243                Some(ResolvedType::Primitive(u256_path))
244            }
245            _ => None,
246        };
247
248        Ok(maybe_resolved)
249    }
250
251    fn try_as_bits256(
252        &self,
253        type_application: &FullTypeApplication,
254    ) -> Result<Option<ResolvedType>> {
255        if type_application.type_decl.type_field != "b256" {
256            return Ok(None);
257        }
258
259        let path = TypePath::new("::fuels::types::Bits256").expect("to be valid");
260        Ok(Some(ResolvedType::StructOrEnum {
261            path,
262            generics: vec![],
263        }))
264    }
265
266    fn try_as_raw_slice(
267        &self,
268        type_application: &FullTypeApplication,
269    ) -> Result<Option<ResolvedType>> {
270        if type_application.type_decl.type_field != "raw untyped slice" {
271            return Ok(None);
272        }
273
274        let path = TypePath::new("::fuels::types::RawSlice").expect("this is a valid TypePath");
275        Ok(Some(ResolvedType::StructOrEnum {
276            path,
277            generics: vec![],
278        }))
279    }
280
281    fn try_as_custom_type(
282        &self,
283        type_application: &FullTypeApplication,
284    ) -> Result<Option<ResolvedType>> {
285        let type_decl = &type_application.type_decl;
286
287        if !type_decl.is_custom_type() {
288            return Ok(None);
289        }
290
291        let original_path = type_decl.custom_type_path()?;
292
293        let used_path = sdk_provided_custom_types_lookup()
294            .get(&original_path)
295            .cloned()
296            .unwrap_or_else(|| original_path.relative_path_from(&self.current_mod));
297
298        let generics = self.resolve_multiple(&type_application.type_arguments)?;
299
300        Ok(Some(ResolvedType::StructOrEnum {
301            path: used_path,
302            generics,
303        }))
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use std::{collections::HashMap, str::FromStr};
310
311    use fuel_abi_types::{
312        abi::{
313            full_program::FullTypeDeclaration,
314            unified_program::{UnifiedTypeApplication, UnifiedTypeDeclaration},
315        },
316        utils::ident,
317    };
318
319    use super::*;
320
321    #[test]
322    fn correctly_extracts_used_generics() {
323        let resolved_type = ResolvedType::StructOrEnum {
324            path: Default::default(),
325            generics: vec![
326                ResolvedType::Tuple(vec![ResolvedType::Array(
327                    Box::new(ResolvedType::StructOrEnum {
328                        path: Default::default(),
329                        generics: vec![
330                            ResolvedType::Generic(GenericType::Named(ident("A"))),
331                            ResolvedType::Generic(GenericType::Constant(10)),
332                        ],
333                    }),
334                    2,
335                )]),
336                ResolvedType::Generic(GenericType::Named(ident("B"))),
337            ],
338        };
339
340        let generics = resolved_type.generics();
341
342        assert_eq!(
343            generics,
344            vec![
345                GenericType::Named(ident("A")),
346                GenericType::Constant(10),
347                GenericType::Named(ident("B"))
348            ]
349        )
350    }
351
352    fn test_resolve_first_type(
353        expected: &str,
354        type_declarations: &[UnifiedTypeDeclaration],
355    ) -> Result<()> {
356        let types = type_declarations
357            .iter()
358            .map(|td| (td.type_id, td.clone()))
359            .collect::<HashMap<_, _>>();
360        let type_application = UnifiedTypeApplication {
361            type_id: type_declarations[0].type_id,
362            ..Default::default()
363        };
364
365        let application = FullTypeApplication::from_counterpart(&type_application, &types);
366        let resolved_type = TypeResolver::default()
367            .resolve(&application)
368            .map_err(|e| e.combine(error!("failed to resolve {:?}", type_application)))?;
369        let actual = resolved_type.to_token_stream().to_string();
370
371        let expected = TokenStream::from_str(expected).unwrap().to_string();
372        assert_eq!(actual, expected);
373
374        Ok(())
375    }
376
377    fn test_resolve_primitive_type(type_field: &str, expected: &str) -> Result<()> {
378        test_resolve_first_type(
379            expected,
380            &[UnifiedTypeDeclaration {
381                type_id: 0,
382                type_field: type_field.to_string(),
383                ..Default::default()
384            }],
385        )
386    }
387
388    #[test]
389    fn test_resolve_u8() -> Result<()> {
390        test_resolve_primitive_type("u8", "::core::primitive::u8")
391    }
392
393    #[test]
394    fn test_resolve_u16() -> Result<()> {
395        test_resolve_primitive_type("u16", "::core::primitive::u16")
396    }
397
398    #[test]
399    fn test_resolve_u32() -> Result<()> {
400        test_resolve_primitive_type("u32", "::core::primitive::u32")
401    }
402
403    #[test]
404    fn test_resolve_u64() -> Result<()> {
405        test_resolve_primitive_type("u64", "::core::primitive::u64")
406    }
407
408    #[test]
409    fn test_resolve_bool() -> Result<()> {
410        test_resolve_primitive_type("bool", "::core::primitive::bool")
411    }
412
413    #[test]
414    fn test_resolve_b256() -> Result<()> {
415        test_resolve_primitive_type("b256", "::fuels::types::Bits256")
416    }
417
418    #[test]
419    fn test_resolve_unit() -> Result<()> {
420        test_resolve_primitive_type("()", "()")
421    }
422
423    #[test]
424    fn test_resolve_array() -> Result<()> {
425        test_resolve_first_type(
426            "[::core::primitive::u8 ; 3usize]",
427            &[
428                UnifiedTypeDeclaration {
429                    type_id: 0,
430                    type_field: "[u8; 3]".to_string(),
431                    components: Some(vec![UnifiedTypeApplication {
432                        type_id: 1,
433                        ..Default::default()
434                    }]),
435                    ..Default::default()
436                },
437                UnifiedTypeDeclaration {
438                    type_id: 1,
439                    type_field: "u8".to_string(),
440                    ..Default::default()
441                },
442            ],
443        )
444    }
445
446    #[test]
447    fn test_resolve_vector() -> Result<()> {
448        test_resolve_first_type(
449            ":: std :: vec :: Vec",
450            &[
451                UnifiedTypeDeclaration {
452                    type_id: 0,
453                    type_field: "struct std::vec::Vec".to_string(),
454                    components: Some(vec![
455                        UnifiedTypeApplication {
456                            name: "buf".to_string(),
457                            type_id: 2,
458                            type_arguments: Some(vec![UnifiedTypeApplication {
459                                type_id: 1,
460                                ..Default::default()
461                            }]),
462                            error_message: None,
463                        },
464                        UnifiedTypeApplication {
465                            name: "len".to_string(),
466                            type_id: 3,
467                            ..Default::default()
468                        },
469                    ]),
470                    type_parameters: Some(vec![1]),
471                    alias_of: None,
472                },
473                UnifiedTypeDeclaration {
474                    type_id: 1,
475                    type_field: "generic T".to_string(),
476                    ..Default::default()
477                },
478                UnifiedTypeDeclaration {
479                    type_id: 2,
480                    type_field: "raw untyped ptr".to_string(),
481                    ..Default::default()
482                },
483                UnifiedTypeDeclaration {
484                    type_id: 3,
485                    type_field: "struct std::vec::RawVec".to_string(),
486                    components: Some(vec![
487                        UnifiedTypeApplication {
488                            name: "ptr".to_string(),
489                            type_id: 2,
490                            ..Default::default()
491                        },
492                        UnifiedTypeApplication {
493                            name: "cap".to_string(),
494                            type_id: 4,
495                            ..Default::default()
496                        },
497                    ]),
498                    type_parameters: Some(vec![1]),
499                    alias_of: None,
500                },
501                UnifiedTypeDeclaration {
502                    type_id: 4,
503                    type_field: "u64".to_string(),
504                    ..Default::default()
505                },
506                UnifiedTypeDeclaration {
507                    type_id: 5,
508                    type_field: "u8".to_string(),
509                    ..Default::default()
510                },
511            ],
512        )
513    }
514
515    #[test]
516    fn test_resolve_bytes() -> Result<()> {
517        test_resolve_first_type(
518            ":: fuels :: types :: Bytes",
519            &[
520                UnifiedTypeDeclaration {
521                    type_id: 0,
522                    type_field: "struct String".to_string(),
523                    components: Some(vec![UnifiedTypeApplication {
524                        name: "bytes".to_string(),
525                        type_id: 1,
526                        ..Default::default()
527                    }]),
528                    ..Default::default()
529                },
530                UnifiedTypeDeclaration {
531                    type_id: 0,
532                    type_field: "struct std::bytes::Bytes".to_string(),
533                    components: Some(vec![
534                        UnifiedTypeApplication {
535                            name: "buf".to_string(),
536                            type_id: 1,
537                            ..Default::default()
538                        },
539                        UnifiedTypeApplication {
540                            name: "len".to_string(),
541                            type_id: 3,
542                            ..Default::default()
543                        },
544                    ]),
545                    ..Default::default()
546                },
547                UnifiedTypeDeclaration {
548                    type_id: 1,
549                    type_field: "struct std::bytes::RawBytes".to_string(),
550                    components: Some(vec![
551                        UnifiedTypeApplication {
552                            name: "ptr".to_string(),
553                            type_id: 2,
554                            ..Default::default()
555                        },
556                        UnifiedTypeApplication {
557                            name: "cap".to_string(),
558                            type_id: 3,
559                            ..Default::default()
560                        },
561                    ]),
562                    ..Default::default()
563                },
564                UnifiedTypeDeclaration {
565                    type_id: 2,
566                    type_field: "raw untyped ptr".to_string(),
567                    ..Default::default()
568                },
569                UnifiedTypeDeclaration {
570                    type_id: 3,
571                    type_field: "u64".to_string(),
572                    ..Default::default()
573                },
574            ],
575        )
576    }
577
578    #[test]
579    fn test_resolve_std_string() -> Result<()> {
580        test_resolve_first_type(
581            ":: std :: string :: String",
582            &[
583                UnifiedTypeDeclaration {
584                    type_id: 0,
585                    type_field: "struct std::string::String".to_string(),
586                    components: Some(vec![UnifiedTypeApplication {
587                        name: "bytes".to_string(),
588                        type_id: 1,
589                        ..Default::default()
590                    }]),
591                    ..Default::default()
592                },
593                UnifiedTypeDeclaration {
594                    type_id: 1,
595                    type_field: "struct std::bytes::Bytes".to_string(),
596                    components: Some(vec![
597                        UnifiedTypeApplication {
598                            name: "buf".to_string(),
599                            type_id: 2,
600                            ..Default::default()
601                        },
602                        UnifiedTypeApplication {
603                            name: "len".to_string(),
604                            type_id: 4,
605                            ..Default::default()
606                        },
607                    ]),
608                    ..Default::default()
609                },
610                UnifiedTypeDeclaration {
611                    type_id: 2,
612                    type_field: "struct std::bytes::RawBytes".to_string(),
613                    components: Some(vec![
614                        UnifiedTypeApplication {
615                            name: "ptr".to_string(),
616                            type_id: 3,
617                            ..Default::default()
618                        },
619                        UnifiedTypeApplication {
620                            name: "cap".to_string(),
621                            type_id: 4,
622                            ..Default::default()
623                        },
624                    ]),
625                    ..Default::default()
626                },
627                UnifiedTypeDeclaration {
628                    type_id: 3,
629                    type_field: "raw untyped ptr".to_string(),
630                    ..Default::default()
631                },
632                UnifiedTypeDeclaration {
633                    type_id: 4,
634                    type_field: "u64".to_string(),
635                    ..Default::default()
636                },
637            ],
638        )
639    }
640
641    #[test]
642    fn test_resolve_static_str() -> Result<()> {
643        test_resolve_primitive_type("str[3]", ":: fuels :: types :: SizedAsciiString < 3usize >")
644    }
645
646    #[test]
647    fn test_resolve_struct() -> Result<()> {
648        test_resolve_first_type(
649            "self :: SomeStruct",
650            &[
651                UnifiedTypeDeclaration {
652                    type_id: 0,
653                    type_field: "struct SomeStruct".to_string(),
654                    components: Some(vec![
655                        UnifiedTypeApplication {
656                            name: "foo".to_string(),
657                            type_id: 1,
658                            ..Default::default()
659                        },
660                        UnifiedTypeApplication {
661                            name: "bar".to_string(),
662                            type_id: 2,
663                            ..Default::default()
664                        },
665                    ]),
666                    type_parameters: Some(vec![1]),
667                    alias_of: None,
668                },
669                UnifiedTypeDeclaration {
670                    type_id: 1,
671                    type_field: "generic T".to_string(),
672                    ..Default::default()
673                },
674                UnifiedTypeDeclaration {
675                    type_id: 2,
676                    type_field: "u8".to_string(),
677                    ..Default::default()
678                },
679            ],
680        )
681    }
682
683    #[test]
684    fn test_resolve_enum() -> Result<()> {
685        test_resolve_first_type(
686            "self :: SomeEnum",
687            &[
688                UnifiedTypeDeclaration {
689                    type_id: 0,
690                    type_field: "enum SomeEnum".to_string(),
691                    components: Some(vec![
692                        UnifiedTypeApplication {
693                            name: "foo".to_string(),
694                            type_id: 1,
695                            ..Default::default()
696                        },
697                        UnifiedTypeApplication {
698                            name: "bar".to_string(),
699                            type_id: 2,
700                            ..Default::default()
701                        },
702                    ]),
703                    type_parameters: Some(vec![1]),
704                    alias_of: None,
705                },
706                UnifiedTypeDeclaration {
707                    type_id: 1,
708                    type_field: "generic T".to_string(),
709                    ..Default::default()
710                },
711                UnifiedTypeDeclaration {
712                    type_id: 2,
713                    type_field: "u8".to_string(),
714                    ..Default::default()
715                },
716            ],
717        )
718    }
719
720    #[test]
721    fn test_resolve_tuple() -> Result<()> {
722        test_resolve_first_type(
723            "(::core::primitive::u8, ::core::primitive::u16, ::core::primitive::bool, T,)",
724            &[
725                UnifiedTypeDeclaration {
726                    type_id: 0,
727                    type_field: "(u8, u16, bool, T)".to_string(),
728                    components: Some(vec![
729                        UnifiedTypeApplication {
730                            type_id: 1,
731                            ..Default::default()
732                        },
733                        UnifiedTypeApplication {
734                            type_id: 2,
735                            ..Default::default()
736                        },
737                        UnifiedTypeApplication {
738                            type_id: 3,
739                            ..Default::default()
740                        },
741                        UnifiedTypeApplication {
742                            type_id: 4,
743                            ..Default::default()
744                        },
745                    ]),
746                    type_parameters: Some(vec![4]),
747                    alias_of: None,
748                },
749                UnifiedTypeDeclaration {
750                    type_id: 1,
751                    type_field: "u8".to_string(),
752                    ..Default::default()
753                },
754                UnifiedTypeDeclaration {
755                    type_id: 2,
756                    type_field: "u16".to_string(),
757                    ..Default::default()
758                },
759                UnifiedTypeDeclaration {
760                    type_id: 3,
761                    type_field: "bool".to_string(),
762                    ..Default::default()
763                },
764                UnifiedTypeDeclaration {
765                    type_id: 4,
766                    type_field: "generic T".to_string(),
767                    ..Default::default()
768                },
769            ],
770        )
771    }
772
773    #[test]
774    fn custom_types_uses_correct_path_for_sdk_provided_types() {
775        let resolver = TypeResolver::default();
776        for (type_path, expected_path) in sdk_provided_custom_types_lookup() {
777            // given
778            let type_application = given_fn_arg_of_custom_type(&type_path);
779
780            // when
781            let resolved_type = resolver.resolve(&type_application).unwrap();
782
783            // then
784            let expected_type_name = expected_path.into_token_stream();
785            assert_eq!(
786                resolved_type.to_token_stream().to_string(),
787                expected_type_name.to_string()
788            );
789        }
790    }
791
792    fn given_fn_arg_of_custom_type(type_path: &TypePath) -> FullTypeApplication {
793        FullTypeApplication {
794            name: "some_arg".to_string(),
795            type_decl: FullTypeDeclaration {
796                type_field: format!("struct {type_path}"),
797                components: vec![],
798                type_parameters: vec![],
799                alias_of: None,
800            },
801            type_arguments: vec![],
802            error_message: None,
803        }
804    }
805}