Skip to main content

dodo_bindgen_codegen/
ty.rs

1use std::collections::BTreeSet;
2
3use crate::util::*;
4use crate::{ClassDefs, FfiInterface, FfiType, TsFormat, TsModule};
5use dodo_bindgen_rust_ir::{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    Char,
35    FnPtr(String), // pointer object containing actual reference value and signature value
36    Ptr(Box<RustType>),
37    PtrMut(Box<RustType>),
38    Ref(Box<RustType>),
39    RefMut(Box<RustType>),
40    Box(Box<RustType>),
41    Str,
42    String,
43    Slice(Box<RustType>),
44    Vec(Box<RustType>),
45    Tuple(Vec<RustType>),
46    UserDefined(Ident),
47    Unsupported, // generic deno pointer object
48}
49
50/* -------------------------------------------------------------------------- */
51
52// MARK:  transform
53
54pub trait TypeExt {
55    fn unwrap(self, module: &mut TsModule) -> (FfiType, RustType);
56}
57
58impl TypeExt for Type {
59    fn unwrap(self, module: &mut TsModule) -> (FfiType, RustType) {
60        let TsModule {
61            type_defs,
62            user_defs,
63            ..
64        } = module;
65
66        fn match_str_or_slice(
67            elem: Box<Type>,
68            module: &mut TsModule,
69            fallback: &dyn Fn(Box<Type>, &mut TsModule) -> RustType,
70        ) -> RustType {
71            match *elem {
72                Type::Str => {
73                    module.type_defs.insert(RustTypeList::Str);
74                    RustType::Str
75                },
76                Type::Slice(elem) => {
77                    module.type_defs.insert(RustTypeList::Slice);
78                    let elem = Box::new(elem.unwrap(module).1);
79                    RustType::Slice(elem)
80                },
81                _ => fallback(elem, module),
82            }
83        }
84
85        match self {
86            Type::Void => (FfiType::Void, RustType::Void),
87            Type::Numeric(type_numeric) => match type_numeric {
88                TypeNumeric::U8 => {
89                    type_defs.insert(RustTypeList::U8);
90                    (FfiType::U8, RustType::Numeric(RustTypeNumeric::U8))
91                },
92                TypeNumeric::U16 => {
93                    type_defs.insert(RustTypeList::U16);
94                    (FfiType::U16, RustType::Numeric(RustTypeNumeric::U16))
95                },
96                TypeNumeric::U32 => {
97                    type_defs.insert(RustTypeList::U32);
98                    (FfiType::U32, RustType::Numeric(RustTypeNumeric::U32))
99                },
100                TypeNumeric::I8 => {
101                    type_defs.insert(RustTypeList::I8);
102                    (FfiType::I8, RustType::Numeric(RustTypeNumeric::I8))
103                },
104                TypeNumeric::I16 => {
105                    type_defs.insert(RustTypeList::I16);
106                    (FfiType::I16, RustType::Numeric(RustTypeNumeric::I16))
107                },
108                TypeNumeric::I32 => {
109                    type_defs.insert(RustTypeList::I32);
110                    (FfiType::I32, RustType::Numeric(RustTypeNumeric::I32))
111                },
112                TypeNumeric::F32 => {
113                    type_defs.insert(RustTypeList::F32);
114                    (FfiType::F32, RustType::Numeric(RustTypeNumeric::F32))
115                },
116                TypeNumeric::F64 => {
117                    type_defs.insert(RustTypeList::F64);
118                    (FfiType::F64, RustType::Numeric(RustTypeNumeric::F64))
119                },
120                TypeNumeric::U64 => {
121                    type_defs.insert(RustTypeList::U64);
122                    (FfiType::U64, RustType::Numeric(RustTypeNumeric::U64))
123                },
124                TypeNumeric::I64 => {
125                    type_defs.insert(RustTypeList::I64);
126                    (FfiType::I64, RustType::Numeric(RustTypeNumeric::I64))
127                },
128                TypeNumeric::Usize => {
129                    type_defs.insert(RustTypeList::Usize);
130                    (FfiType::Usize, RustType::Numeric(RustTypeNumeric::Usize))
131                },
132                TypeNumeric::Isize => {
133                    type_defs.insert(RustTypeList::Isize);
134                    (FfiType::Isize, RustType::Numeric(RustTypeNumeric::Isize))
135                },
136            },
137            Type::Bool => (FfiType::Bool, RustType::Boolean),
138            Type::Char => {
139                type_defs.insert(RustTypeList::Char);
140                (FfiType::U32, RustType::Char)
141            },
142            Type::FnPtr(type_bare_fn) => {
143                type_defs.insert(RustTypeList::FnPtr);
144                (
145                    FfiType::FnPointer,
146                    RustType::FnPtr(type_bare_fn.to_token_stream().to_string()),
147                )
148            },
149            rest => (
150                FfiType::Pointer,
151                match rest {
152                    Type::Ptr(elem) => match_str_or_slice(elem, module, &|elem, module| {
153                        module.type_defs.insert(RustTypeList::Ptr);
154                        let elem = Box::new(elem.unwrap(module).1);
155                        RustType::Ptr(elem)
156                    }),
157                    Type::PtrMut(elem) => match_str_or_slice(elem, module, &|elem, module| {
158                        module.type_defs.insert(RustTypeList::PtrMut);
159                        let elem = Box::new(elem.unwrap(module).1);
160                        RustType::PtrMut(elem)
161                    }),
162                    Type::Ref(elem) => match_str_or_slice(elem, module, &|elem, module| {
163                        module.type_defs.insert(RustTypeList::Ref);
164                        let elem = Box::new(elem.unwrap(module).1);
165                        RustType::Ref(elem)
166                    }),
167                    Type::RefMut(elem) => match_str_or_slice(elem, module, &|elem, module| {
168                        module.type_defs.insert(RustTypeList::RefMut);
169                        let elem = Box::new(elem.unwrap(module).1);
170                        RustType::RefMut(elem)
171                    }),
172                    Type::Box(elem) => match_str_or_slice(elem, module, &|elem, module| {
173                        module.type_defs.insert(RustTypeList::Box);
174                        RustType::Box(Box::new(elem.unwrap(module).1))
175                    }),
176                    Type::Str => {
177                        type_defs.insert(RustTypeList::Str);
178                        RustType::Str
179                    },
180                    Type::String => {
181                        type_defs.insert(RustTypeList::String);
182                        RustType::String
183                    },
184                    Type::Slice(elem) => {
185                        type_defs.insert(RustTypeList::Slice);
186                        RustType::Slice(Box::new(elem.unwrap(module).1))
187                    },
188                    Type::Array(_) => {
189                        type_defs.insert(RustTypeList::Unsupported);
190                        RustType::Unsupported
191                    },
192                    Type::Vec(elem) => {
193                        type_defs.insert(RustTypeList::Vec);
194                        RustType::Vec(Box::new(elem.unwrap(module).1))
195                    },
196                    Type::UserDefined(ident) => {
197                        user_defs.insert(ident.clone());
198                        RustType::UserDefined(ident)
199                    },
200                    Type::Tuple(elems) => {
201                        type_defs.insert(RustTypeList::Tuple);
202                        let mut tup_elems = Vec::new();
203                        for elem in elems {
204                            tup_elems.push(elem.unwrap(module).1);
205                        }
206                        RustType::Tuple(tup_elems)
207                    },
208                    Type::Unsupported(_) => {
209                        type_defs.insert(RustTypeList::Unsupported);
210                        RustType::Unsupported
211                    },
212                    _ => unreachable!(),
213                },
214            ),
215        }
216    }
217}
218
219/* -------------------------------------------------------------------------- */
220
221// MARK: transform tests
222
223#[cfg(test)]
224mod transform_tests {
225    use super::*;
226    use dodo_bindgen_rust_ir::{dbg_assert, parse_quote};
227
228    macro_rules! unwrap_type {
229        ($($tt:tt)*) => {{
230            let mut module = TsModule::default();
231            let actual = Type::unwrap(parse_quote!(Type, $($tt)*), &mut module);
232            actual.1
233        }};
234    }
235
236    #[test]
237    fn test_ts_type() {
238        dbg_assert!(unwrap_type!(()), RustType::Void);
239    }
240}
241
242/* -------------------------------------------------------------------------- */
243
244// MARK: print
245
246#[rustfmt::skip]
247impl ToTokens for RustTypeNumeric {
248    fn to_tokens(&self, tokens: &mut TokenStream) {
249        tokens.extend(match self {
250            RustTypeNumeric::U8    => quote! { RustU8 },
251            RustTypeNumeric::U16   => quote! { RustU16 },
252            RustTypeNumeric::U32   => quote! { RustU32 },
253            RustTypeNumeric::I8    => quote! { RustI8 },
254            RustTypeNumeric::I16   => quote! { RustI16 },
255            RustTypeNumeric::I32   => quote! { RustI32 },
256            RustTypeNumeric::F32   => quote! { RustF32 },
257            RustTypeNumeric::F64   => quote! { RustF64 },
258            RustTypeNumeric::U64   => quote! { RustU64 },
259            RustTypeNumeric::I64   => quote! { RustI64 },
260            RustTypeNumeric::Usize => quote! { RustUsize },
261            RustTypeNumeric::Isize => quote! { RustIsize },
262        });
263    }
264}
265
266#[rustfmt::skip]
267impl ToTokens for RustType {
268    fn to_tokens(&self, tokens: &mut TokenStream) {
269        fn get_str_or_slice(elem: &Box<RustType>, fallback: TokenStream) -> TokenStream {
270            match &**elem {
271                RustType::Str => quote! { RustStr },
272                RustType::Slice(elem) => quote! { RustSlice<#elem> },
273                _ => fallback,
274            }
275        }
276
277        tokens.extend(match self {
278            RustType::Void => quote! { void },
279            RustType::Numeric(ts_type_numeric) => ts_type_numeric.to_token_stream(),
280            RustType::Boolean => quote! { boolean },
281            RustType::Char => quote! { RustChar },
282            RustType::FnPtr(sig) => quote! { RustFnPtr<#sig> },
283            RustType::Ptr(elem) =>
284            get_str_or_slice(elem, quote! { RustPtr<#elem> }),
285            RustType::PtrMut(elem) =>
286            get_str_or_slice(elem, quote! { RustPtrMut<#elem> }),
287            RustType::Ref(elem) =>
288            get_str_or_slice(elem, quote! { RustRef<#elem> }),
289            RustType::RefMut(elem) =>
290            get_str_or_slice(elem, quote! { RustRefMut<#elem> }),
291            RustType::Box(elem) =>
292            get_str_or_slice(elem, quote! { RustBox<#elem> }),
293            RustType::Str => quote! { RustStr },
294            RustType::String => quote! { RustString },
295            RustType::Slice(elem) => quote! { RustSlice<#elem> },
296            RustType::Vec(elem) => quote! { RustVec<#elem> },
297            RustType::Tuple(elem) => quote! { RustTuple<[#(#elem),*]> },
298            RustType::UserDefined(ident) => quote! { #ident },
299            RustType::Unsupported => quote! { RustUnsupportedType },
300        });
301    }
302}
303
304/* -------------------------------------------------------------------------- */
305
306// MARK: user defined
307
308/// Container for user-defined types that do not have implementations or not
309/// included in the list of declared structs
310#[derive(Clone, Debug, Default)]
311pub struct UserDefinedDefs {
312    store: BTreeSet<Ident>,
313}
314
315impl UserDefinedDefs {
316    fn insert(&mut self, ident: Ident) {
317        self.store.insert(ident);
318    }
319
320    /// removes any user defined type from this list if it matches that from the
321    /// list of class definitions
322    pub fn dedup(&mut self, class_defs: &ClassDefs) {
323        class_defs.store.keys().for_each(|class_name| {
324            self.store
325                .extract_if(.., |user_defined| *user_defined == *class_name)
326                .for_each(drop);
327        });
328    }
329}
330
331impl ToTokens for UserDefinedDefs {
332    fn to_tokens(&self, tokens: &mut TokenStream) {
333        tokens.extend(
334            self.store
335                .iter()
336                .map(|ident| {
337                    quote! {
338
339                        export class #ident extends RustPrototype<#ident> {}
340
341                    }
342                })
343                .collect::<TokenStream>(),
344        );
345    }
346}
347
348/* -------------------------------------------------------------------------- */
349
350#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
351enum RustTypeList {
352    U8,
353    U16,
354    U32,
355    I8,
356    I16,
357    I32,
358    F32,
359    F64,
360    U64,
361    Usize,
362    I64,
363    Isize,
364    Char,
365    FnPtr,
366    Ptr,
367    PtrMut,
368    Ref,
369    RefMut,
370    Box,
371    Str,
372    String,
373    Slice,
374    Vec,
375    Tuple,
376    Unsupported,
377    // types not checked:
378    // Void,
379    // Boolean,
380    // UserDefined,
381}
382
383impl ToTokens for RustTypeList {
384    fn to_tokens(&self, tokens: &mut TokenStream) {
385        tokens.extend(match self {
386            RustTypeList::U8 => quote! { RustU8 },
387            RustTypeList::U16 => quote! { RustU16 },
388            RustTypeList::U32 => quote! { RustU32 },
389            RustTypeList::I8 => quote! { RustI8 },
390            RustTypeList::I16 => quote! { RustI16 },
391            RustTypeList::I32 => quote! { RustI32 },
392            RustTypeList::F32 => quote! { RustF32 },
393            RustTypeList::F64 => quote! { RustF64 },
394            RustTypeList::U64 => quote! { RustU64 },
395            RustTypeList::Usize => quote! { RustUsize },
396            RustTypeList::I64 => quote! { RustI64 },
397            RustTypeList::Isize => quote! { RustIsize },
398            RustTypeList::Char => quote! { RustChar },
399            RustTypeList::FnPtr => quote! { RustFnPtr },
400            RustTypeList::Ptr => quote! { RustPtr },
401            RustTypeList::PtrMut => quote! { RustPtrMut },
402            RustTypeList::Ref => quote! { RustRef },
403            RustTypeList::RefMut => quote! { RustRefMut },
404            RustTypeList::Box => quote! { RustBox },
405            RustTypeList::Str => quote! { RustStr },
406            RustTypeList::String => quote! { RustString },
407            RustTypeList::Slice => quote! { RustSlice },
408            RustTypeList::Vec => quote! { RustVec },
409            RustTypeList::Tuple => quote! { RustTuple },
410            RustTypeList::Unsupported => quote! { RustUnsupportedType },
411        });
412    }
413}
414
415/* -------------------------------------------------------------------------- */
416
417struct FileStr<'a> {
418    store: &'a str,
419}
420impl<'a> FileStr<'a> {
421    fn new(source: &'a str) -> Self {
422        Self { store: source }
423    }
424    /// Extract a string slice matched between a start and end marker and return
425    /// the inner module as a String
426    fn slice_between(self, start_marker: &'a str, end_marker: &'a str) -> &'a str {
427        self.store
428            .split(start_marker)
429            .nth(1)
430            .expect("failed to match a start marker")
431            .split(end_marker)
432            .next()
433            .expect("start marker is missing a closing delimiter")
434    }
435    /// Find zero or more occurences of a section matched by a start and end
436    /// marker and take it out of a string, returning the result after taking
437    /// out those sections
438    fn slice_out(self, start_marker: &'a str, end_marker: &'a str) -> Vec<&'a str> {
439        let mut out = Vec::new();
440        let match_count = self.store.matches(start_marker).count();
441        if match_count > 0 {
442            if match_count == self.store.matches(end_marker).count() {
443                let mut rest = self.store;
444                for _ in 0..match_count {
445                    let (left, right) = rest.split_once(start_marker).unwrap();
446                    out.push(left);
447                    let (_, right) = right.split_once(end_marker).unwrap();
448                    rest = right;
449                }
450                out.push(rest);
451            } else {
452                panic!("slice out marker is missing a closing pair");
453            }
454        } else {
455            out.push(self.store);
456        }
457        out
458    }
459}
460
461/* -------------------------------------------------------------------------- */
462
463// MARK: rust type defs
464
465/// Helper for generating typescript declarations/representations of standard
466/// rust types. These types should not be exposed to downstream external users
467/// of the generated ffi library to prevent accidental misuse of the API
468#[derive(Clone, Debug, Default)]
469pub struct RustTypeDefs {
470    store: BTreeSet<RustTypeList>,
471    /// Whether to use the extended variants of the rust type declarations which
472    /// include methods for interacting with the underlying rust data structure
473    ///
474    /// If using extended types, prompt user whether to embed the string utility
475    /// functions as a dylib in the user's library or to fetch it remotely
476    pub extended: bool,
477}
478
479impl RustTypeDefs {
480    fn insert(&mut self, ty: RustTypeList) {
481        self.store.insert(ty);
482    }
483}
484
485impl RustTypeDefs {
486    /// Print rust types inline
487    pub fn print_inline(&self) -> (String, FfiInterface) {
488        const CONTENT_START: &str = "// <!-- deno-bindgen2-content-start -->\n";
489        const CONTENT_END: &str = "// <!-- deno-bindgen2-content-end -->\n";
490        const ALT_START: &str = "// <!-- deno-bindgen2-alt-type-start -->\n";
491        const ALT_END: &str = "// <!-- deno-bindgen2-alt-type-end -->\n";
492
493        let util_file = include_str!("../ts/util.ts");
494        let core_file = include_str!("../ts/core.ts");
495        let extended_file = include_str!("../ts/extended.ts");
496
497        let util = FileStr::new(&util_file).slice_between(CONTENT_START, CONTENT_END);
498        let core_types = FileStr::new(&core_file).slice_between(CONTENT_START, CONTENT_END);
499
500        let ffi_interface;
501
502        let mut module = String::new();
503        module.push_str(util);
504        module.push_str(core_types);
505        if self.extended {
506            let extended_types = FileStr::new(&extended_file).slice_between(ALT_START, ALT_END);
507            let extended_types = FileStr::new(extended_types).slice_out(
508                "// <!-- deno-bindgen2-ignore-start -->",
509                "// <!-- deno-bindgen2-ignore-end -->",
510            );
511            for slice in extended_types {
512                module.push_str(slice);
513            }
514
515            let _ffi_interface = <TokenStream as std::str::FromStr>::from_str(
516                FileStr::new(&extended_file).slice_between(
517                    "    // <!-- deno-bindgen2-ffi-symbols-start -->\n",
518                    "    // <!-- deno-bindgen2-ffi-symbols-end -->\n",
519                ),
520            )
521            .expect("failed to parse ffi symbol tokens");
522
523            ffi_interface = syn::parse2(_ffi_interface).expect("failed to parse ffi interface");
524        } else {
525            let core_alt_types = FileStr::new(&core_file).slice_between(ALT_START, ALT_END);
526            module.push_str(core_alt_types);
527            ffi_interface = FfiInterface::default();
528        }
529
530        (module, ffi_interface)
531    }
532
533    /// Print rust types as a new file, along with an import statement
534    pub fn print_separate(
535        &self,
536        mut type_defs: String, // contents of type_defs module output
537        type_defs_name: &str,
538    ) -> (String, String) {
539        let mut type_imports = Vec::new();
540        let mut class_imports = Vec::new();
541
542        for ty in &self.store {
543            match ty {
544                RustTypeList::Char
545                | RustTypeList::Box
546                | RustTypeList::Str
547                | RustTypeList::String
548                | RustTypeList::Slice
549                | RustTypeList::Vec
550                | RustTypeList::Tuple => class_imports.push(ty.to_token_stream()),
551                rest => type_imports.push(rest.to_token_stream()),
552            };
553        }
554
555        if type_imports.is_empty() && class_imports.is_empty() {
556            (String::new(), String::new())
557        } else {
558            class_imports.push(quote! { RustPrototype });
559
560            type_defs.push_str(
561                TsFormat::format(
562                    quote! {
563
564                        export type { #(#type_imports),* };
565                        export { #(#class_imports),* };
566
567                    }
568                    .to_string(),
569                )
570                .as_str(),
571            );
572
573            let type_defs_name = format!("./{type_defs_name}");
574            let imports = TsFormat::format(
575                quote! {
576                    import type { #(#type_imports),* } from #type_defs_name;
577                    import { #(#class_imports),* } from #type_defs_name;
578                }
579                .to_string(),
580            );
581
582            (type_defs, imports)
583        }
584    }
585}
586
587/* -------------------------------------------------------------------------- */
588
589// MARK: print tests
590
591#[cfg(test)]
592mod print_tests {
593    use super::*;
594    use crate::{FfiLib, TsFormat};
595
596    #[test]
597    fn test_include_str() {
598        let type_defs = RustTypeDefs::default();
599        let (module, _) = type_defs.print_inline();
600        println!("{module}");
601    }
602
603    #[test]
604    fn test_print() {}
605
606    #[test]
607    fn print_type_def() {
608        let mut type_defs = RustTypeDefs::default();
609        type_defs.extended = true;
610        let (module, ffi_interface) = type_defs.print_inline();
611        let mut ffi_lib = FfiLib::default();
612        ffi_lib.interface = ffi_interface;
613
614        println!(
615            "{}",
616            TsFormat::format(ffi_lib.to_token_stream().to_string())
617        );
618        println!("{module}");
619    }
620}