Skip to main content

dodo_bindgen_codegen/
ty.rs

1use std::collections::BTreeSet;
2
3use crate::util::*;
4use crate::{ClassDefs, FfiType, TsModule};
5use dodo_bindgen_rust_ir::{Abi, Type, TypeNumeric};
6use proc_macro2::TokenStream;
7use syn::Ident;
8
9/* -------------------------------------------------------------------------- */
10
11// MARK: rust type
12
13#[derive(Clone, Debug, PartialEq)]
14pub enum RustTypeNumeric {
15    U8,
16    U16,
17    U32,
18    I8,
19    I16,
20    I32,
21    F32,
22    F64,
23    U64,
24    I64,
25    Usize,
26    Isize,
27}
28
29#[derive(Clone, Debug, PartialEq)]
30pub enum RustType {
31    Void,
32    Numeric(RustTypeNumeric),
33    Boolean,
34    FnPtr(String), // pointer object containing actual reference value and signature value
35    Ptr(Box<RustType>),
36    PtrMut(Box<RustType>),
37    Ref(Box<RustType>),
38    RefMut(Box<RustType>),
39    Box(Box<RustType>),
40    Str,
41    Slice(Box<RustType>),
42    Vec(Box<RustType>),
43    Tuple(Vec<RustType>),
44    UserDefined(Ident),
45    Unsupported, // generic deno pointer object
46    Custom {
47        ts_type: String,
48        to_rust: String,
49        from_rust: String,
50    },
51}
52
53fn numeric(type_numeric: TypeNumeric) -> RustTypeNumeric {
54    match type_numeric {
55        TypeNumeric::U8 => RustTypeNumeric::U8,
56        TypeNumeric::U16 => RustTypeNumeric::U16,
57        TypeNumeric::U32 => RustTypeNumeric::U32,
58        TypeNumeric::I8 => RustTypeNumeric::I8,
59        TypeNumeric::I16 => RustTypeNumeric::I16,
60        TypeNumeric::I32 => RustTypeNumeric::I32,
61        TypeNumeric::F32 => RustTypeNumeric::F32,
62        TypeNumeric::F64 => RustTypeNumeric::F64,
63        TypeNumeric::U64 => RustTypeNumeric::U64,
64        TypeNumeric::I64 => RustTypeNumeric::I64,
65        TypeNumeric::Usize => RustTypeNumeric::Usize,
66        TypeNumeric::Isize => RustTypeNumeric::Isize,
67    }
68}
69
70/* -------------------------------------------------------------------------- */
71
72// MARK:  transform
73
74pub trait TypeExt {
75    fn unwrap(self, module: &mut TsModule) -> RustType;
76}
77
78impl TypeExt for Type {
79    fn unwrap(self, module: &mut TsModule) -> RustType {
80        fn match_str_or_slice(
81            elem: Box<Type>,
82            module: &mut TsModule,
83            fallback: &dyn Fn(Box<Type>, &mut TsModule) -> RustType,
84        ) -> RustType {
85            match *elem {
86                Type::Str => {
87                    module.type_defs.insert(RustTypeList::Str);
88                    RustType::Str
89                },
90                Type::Slice(elem) => {
91                    module.type_defs.insert(RustTypeList::Slice);
92                    let elem = Box::new(elem.unwrap(module));
93                    RustType::Slice(elem)
94                },
95                _ => fallback(elem, module),
96            }
97        }
98
99        if let Some(class) = module.class_types.get(&self) {
100            return RustType::UserDefined(class.clone());
101        }
102
103        let type_defs = &mut module.type_defs;
104        match self {
105            Type::Void => RustType::Void,
106            Type::Numeric(type_numeric) => RustType::Numeric(numeric(type_numeric)),
107            Type::Bool => RustType::Boolean,
108            fn_ptr @ Type::FnPtr { .. } => {
109                type_defs.insert(RustTypeList::FnPtr);
110                RustType::FnPtr(fn_ptr.to_string())
111            },
112            Type::Ptr(elem) => match_str_or_slice(elem, module, &|elem, module| {
113                module.type_defs.insert(RustTypeList::Ptr);
114                RustType::Ptr(Box::new(elem.unwrap(module)))
115            }),
116            Type::PtrMut(elem) => match_str_or_slice(elem, module, &|elem, module| {
117                module.type_defs.insert(RustTypeList::PtrMut);
118                RustType::PtrMut(Box::new(elem.unwrap(module)))
119            }),
120            Type::Ref(elem) => match_str_or_slice(elem, module, &|elem, module| {
121                module.type_defs.insert(RustTypeList::Ref);
122                RustType::Ref(Box::new(elem.unwrap(module)))
123            }),
124            Type::RefMut(elem) => match_str_or_slice(elem, module, &|elem, module| {
125                module.type_defs.insert(RustTypeList::RefMut);
126                RustType::RefMut(Box::new(elem.unwrap(module)))
127            }),
128            Type::Box(elem) => match_str_or_slice(elem, module, &|elem, module| {
129                module.type_defs.insert(RustTypeList::Box);
130                RustType::Box(Box::new(elem.unwrap(module)))
131            }),
132            Type::Str => {
133                type_defs.insert(RustTypeList::Str);
134                RustType::Str
135            },
136            Type::Slice(elem) => {
137                type_defs.insert(RustTypeList::Slice);
138                RustType::Slice(Box::new(elem.unwrap(module)))
139            },
140            Type::Vec(elem) => {
141                type_defs.insert(RustTypeList::Vec);
142                RustType::Vec(Box::new(elem.unwrap(module)))
143            },
144            Type::Tuple(elems) => {
145                type_defs.insert(RustTypeList::Tuple);
146                RustType::Tuple(elems.into_iter().map(|elem| elem.unwrap(module)).collect())
147            },
148            Type::Object { name, .. } => {
149                let ident = format_ident!("{}", name);
150                module.user_defs.insert(ident.clone());
151                RustType::UserDefined(ident)
152            },
153            Type::Custom {
154                ts_type,
155                to_rust,
156                from_rust,
157                helper,
158            } => {
159                if !helper.is_empty() {
160                    module.used_helpers.insert(helper);
161                }
162                RustType::Custom {
163                    ts_type,
164                    to_rust,
165                    from_rust,
166                }
167            },
168            Type::String | Type::Array { .. } | Type::Opaque => {
169                type_defs.insert(RustTypeList::Unsupported);
170                RustType::Unsupported
171            },
172        }
173    }
174}
175
176impl From<Abi> for FfiType {
177    fn from(abi: Abi) -> Self {
178        match abi {
179            Abi::Void => FfiType::Void,
180            Abi::U8 => FfiType::U8,
181            Abi::U16 => FfiType::U16,
182            Abi::U32 => FfiType::U32,
183            Abi::U64 => FfiType::U64,
184            Abi::Usize => FfiType::Usize,
185            Abi::I8 => FfiType::I8,
186            Abi::I16 => FfiType::I16,
187            Abi::I32 => FfiType::I32,
188            Abi::I64 => FfiType::I64,
189            Abi::Isize => FfiType::Isize,
190            Abi::F32 => FfiType::F32,
191            Abi::F64 => FfiType::F64,
192            Abi::Bool => FfiType::Bool,
193            Abi::Pointer => FfiType::Pointer,
194            Abi::Buffer => FfiType::Buffer,
195            Abi::Function => FfiType::FnPointer,
196        }
197    }
198}
199
200/* -------------------------------------------------------------------------- */
201
202// MARK: transform tests
203
204#[cfg(test)]
205mod transform_tests {
206    use super::*;
207
208    #[test]
209    fn test_ts_type() {
210        let mut module = TsModule::default();
211        assert_eq!(Type::Void.unwrap(&mut module), RustType::Void);
212        assert_eq!(
213            Type::Ref(Box::new(Type::Str)).unwrap(&mut module),
214            RustType::Str
215        );
216        assert_eq!(
217            Type::FnPtr {
218                inputs: vec![Type::Numeric(TypeNumeric::U8)],
219                output: Box::new(Type::Numeric(TypeNumeric::U8)),
220            }
221            .unwrap(&mut module),
222            RustType::FnPtr("extern \"C\" fn(u8) -> u8".to_string())
223        );
224    }
225
226    #[test]
227    fn test_custom_records_helper() {
228        let mut module = TsModule::default();
229        let ty = Type::Custom {
230            ts_type: "string".to_string(),
231            to_rust: "encode({})".to_string(),
232            from_rust: "decode({})".to_string(),
233            helper: "codec".to_string(),
234        };
235        assert!(matches!(ty.unwrap(&mut module), RustType::Custom { .. }));
236        assert!(module.used_helpers.contains("codec"));
237    }
238}
239
240/* -------------------------------------------------------------------------- */
241
242// MARK: print
243
244impl ToTokens for RustTypeNumeric {
245    fn to_tokens(&self, tokens: &mut TokenStream) {
246        tokens.extend(match self {
247            RustTypeNumeric::U64
248            | RustTypeNumeric::I64
249            | RustTypeNumeric::Usize
250            | RustTypeNumeric::Isize => quote! { bigint },
251            _ => quote! { number },
252        });
253    }
254}
255
256#[rustfmt::skip]
257impl ToTokens for RustType {
258    fn to_tokens(&self, tokens: &mut TokenStream) {
259        fn get_str_or_slice(elem: &Box<RustType>, fallback: TokenStream) -> TokenStream {
260            match &**elem {
261                RustType::Str => quote! { RustStr },
262                RustType::Slice(elem) => quote! { RustSlice<#elem> },
263                _ => fallback,
264            }
265        }
266
267        tokens.extend(match self {
268            RustType::Void => quote! { void },
269            RustType::Numeric(ts_type_numeric) => ts_type_numeric.to_token_stream(),
270            RustType::Boolean => quote! { boolean },
271            RustType::FnPtr(sig) => quote! { RustFnPtr<#sig> },
272            RustType::Ptr(elem) =>
273            get_str_or_slice(elem, quote! { RustPtr<#elem> }),
274            RustType::PtrMut(elem) =>
275            get_str_or_slice(elem, quote! { RustPtrMut<#elem> }),
276            RustType::Ref(elem) =>
277            get_str_or_slice(elem, quote! { RustRef<#elem> }),
278            RustType::RefMut(elem) =>
279            get_str_or_slice(elem, quote! { RustRefMut<#elem> }),
280            RustType::Box(elem) =>
281            get_str_or_slice(elem, quote! { RustBox<#elem> }),
282            RustType::Str => quote! { RustStr },
283            RustType::Slice(elem) => quote! { RustSlice<#elem> },
284            RustType::Vec(elem) => quote! { RustVec<#elem> },
285            RustType::Tuple(elem) => quote! { RustTuple<[#(#elem),*]> },
286            RustType::UserDefined(ident) => quote! { #ident },
287            RustType::Unsupported => quote! { RustUnsupportedType },
288            RustType::Custom { ts_type, .. } => raw(ts_type.as_str()).to_token_stream(),
289        });
290    }
291}
292
293/* -------------------------------------------------------------------------- */
294
295// MARK: user defined
296
297/// Container for user-defined types that do not have implementations or not
298/// included in the list of declared structs
299#[derive(Clone, Debug, Default)]
300pub struct UserDefinedDefs {
301    store: BTreeSet<Ident>,
302}
303
304impl UserDefinedDefs {
305    fn insert(&mut self, ident: Ident) {
306        self.store.insert(ident);
307    }
308
309    /// removes any user defined type from this list if it matches that from the
310    /// list of class definitions
311    pub fn dedup(&mut self, class_defs: &ClassDefs) {
312        class_defs.store.keys().for_each(|class_name| {
313            self.store
314                .extract_if(.., |user_defined| *user_defined == *class_name)
315                .for_each(drop);
316        });
317    }
318}
319
320impl ToTokens for UserDefinedDefs {
321    fn to_tokens(&self, tokens: &mut TokenStream) {
322        tokens.extend(
323            self.store
324                .iter()
325                .map(|ident| {
326                    quote! {
327
328                        export class #ident extends RustPrototype<#ident> {}
329
330                    }
331                })
332                .collect::<TokenStream>(),
333        );
334    }
335}
336
337/* -------------------------------------------------------------------------- */
338
339#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
340enum RustTypeList {
341    FnPtr,
342    Ptr,
343    PtrMut,
344    Ref,
345    RefMut,
346    Box,
347    Str,
348    Slice,
349    Vec,
350    Tuple,
351    Unsupported,
352    // types not checked:
353    // Void,
354    // Boolean,
355    // UserDefined,
356}
357
358impl ToTokens for RustTypeList {
359    fn to_tokens(&self, tokens: &mut TokenStream) {
360        tokens.extend(match self {
361            RustTypeList::FnPtr => quote! { RustFnPtr },
362            RustTypeList::Ptr => quote! { RustPtr },
363            RustTypeList::PtrMut => quote! { RustPtrMut },
364            RustTypeList::Ref => quote! { RustRef },
365            RustTypeList::RefMut => quote! { RustRefMut },
366            RustTypeList::Box => quote! { RustBox },
367            RustTypeList::Str => quote! { RustStr },
368            RustTypeList::Slice => quote! { RustSlice },
369            RustTypeList::Vec => quote! { RustVec },
370            RustTypeList::Tuple => quote! { RustTuple },
371            RustTypeList::Unsupported => quote! { RustUnsupportedType },
372        });
373    }
374}
375
376/* -------------------------------------------------------------------------- */
377
378// MARK: rust type defs
379
380/// Helper for generating typescript declarations/representations of standard
381/// rust types. These types should not be exposed to downstream external users
382/// of the generated ffi library to prevent accidental misuse of the API
383#[derive(Clone, Debug, Default)]
384pub struct RustTypeDefs {
385    store: BTreeSet<RustTypeList>,
386}
387
388impl RustTypeDefs {
389    fn insert(&mut self, ty: RustTypeList) {
390        self.store.insert(ty);
391    }
392}
393
394impl RustTypeDefs {
395    /// Print rust types as a new file, along with an import statement
396    pub fn print_separate(
397        &self,
398        mut type_defs: String, // contents of type_defs module output
399        type_defs_name: &str,
400    ) -> (String, String) {
401        let mut type_imports = Vec::new();
402        let mut class_imports = vec![
403            quote! { RustPrototype },
404            quote! { __dodo_borrow },
405            quote! { __dodo_borrow_mut },
406            quote! { __dodo_borrow_async },
407            quote! { __dodo_borrow_mut_async },
408            quote! { __dodo_take_async },
409            quote! { __dodo_dispose },
410        ];
411
412        for ty in &self.store {
413            match ty {
414                RustTypeList::Box
415                | RustTypeList::Str
416                | RustTypeList::Slice
417                | RustTypeList::Vec
418                | RustTypeList::Tuple => class_imports.push(ty.to_token_stream()),
419                rest => type_imports.push(rest.to_token_stream()),
420            };
421        }
422
423        type_defs.push('\n');
424        type_defs.push_str(
425            &quote! {
426                export type { #(#type_imports),* };
427                export { #(#class_imports),* };
428            }
429            .to_string(),
430        );
431
432        let type_defs_name = format!("./{type_defs_name}");
433        let type_imports = if type_imports.is_empty() {
434            TokenStream::new()
435        } else {
436            quote! { import type { #(#type_imports),* } from #type_defs_name; }
437        };
438        let imports = quote! {
439            #type_imports
440            import { #(#class_imports),* } from #type_defs_name;
441        }
442        .to_string();
443
444        (type_defs, imports)
445    }
446}