Skip to main content

dodo_bindgen_codegen/
file.rs

1use std::collections::{BTreeSet, HashMap, HashSet};
2
3use crate::ffi::FfiSymbol;
4use crate::util::*;
5use crate::{ClassDefs, FfiFunction, FfiLib, FfiType, FunctionDefs, RustTypeDefs, UserDefinedDefs};
6use dodo_bindgen_rust_ir::{Item, ItemClass, ItemFn, ItemHelper, ItemMethod, ItemTs, Type};
7use syn::Ident;
8
9use super::fn_::ItemFnExt as _;
10
11/* -------------------------------------------------------------------------- */
12
13#[derive(Clone, Debug)]
14pub struct CodegenOpts {
15    pub file_name: String,
16    pub lib_name: String,
17    pub lazy: bool,
18}
19
20#[derive(Clone, Debug, Default)]
21pub struct TsModule {
22    pub ffi_lib: FfiLib, // symbol definitions
23    pub type_defs: RustTypeDefs, /* import statements or type definitions if `inline = true`.
24                          * links to standard types */
25    pub user_defs: UserDefinedDefs,
26    pub functions: FunctionDefs,
27    pub class_defs: ClassDefs,
28    pub class_types: HashMap<Type, Ident>,
29    pub used_helpers: BTreeSet<String>,
30    pub uses_pin: bool,
31    pub runtime: String,
32    pub helpers: String,
33}
34
35const PIN_HELPERS: &str = r#"
36const __dodo_pinned = new Set<unknown[]>();
37"#;
38
39// TODO: use buffer where possible to handle large projects
40
41fn type_contains(haystack: &Type, needle: &Type) -> bool {
42    if haystack == needle {
43        return true;
44    }
45    match haystack {
46        Type::Ptr(elem)
47        | Type::PtrMut(elem)
48        | Type::Ref(elem)
49        | Type::RefMut(elem)
50        | Type::Box(elem)
51        | Type::Slice(elem)
52        | Type::Vec(elem)
53        | Type::Array { elem, .. } => type_contains(elem, needle),
54        Type::Tuple(elems) => elems.iter().any(|elem| type_contains(elem, needle)),
55        Type::FnPtr { inputs, output } => {
56            inputs.iter().any(|input| type_contains(input, needle)) || type_contains(output, needle)
57        },
58        _ => false,
59    }
60}
61
62fn fn_types(func: &ItemFn) -> impl Iterator<Item = &Type> {
63    func.inputs
64        .iter()
65        .map(|arg| &arg.ty)
66        .chain(std::iter::once(&func.output))
67}
68
69fn is_foreign(class: &ItemClass) -> bool {
70    !matches!(class.ty, Type::Object { .. })
71}
72
73fn kept_classes(classes: &[ItemClass], methods: &[ItemMethod], fns: &[ItemFn]) -> HashSet<String> {
74    let mut kept: HashSet<String> = classes
75        .iter()
76        .filter(|class| !is_foreign(class))
77        .map(|class| class.name.clone())
78        .collect();
79    let mut roots: Vec<&Type> = fns.iter().flat_map(fn_types).collect();
80    roots.extend(
81        methods
82            .iter()
83            .filter(|method| kept.contains(&method.class))
84            .flat_map(|method| fn_types(&method.func)),
85    );
86
87    loop {
88        let newly_kept: Vec<&ItemClass> = classes
89            .iter()
90            .filter(|class| is_foreign(class) && !kept.contains(&class.name))
91            .filter(|class| roots.iter().any(|root| type_contains(root, &class.ty)))
92            .collect();
93        if newly_kept.is_empty() {
94            return kept;
95        }
96        for class in newly_kept {
97            kept.insert(class.name.clone());
98            roots.extend(
99                methods
100                    .iter()
101                    .filter(|method| method.class == class.name)
102                    .flat_map(|method| fn_types(&method.func)),
103            );
104        }
105    }
106}
107
108impl TsModule {
109    pub fn new(items: Vec<Item>, opts: &CodegenOpts) -> Self {
110        let mut module = TsModule::default();
111        module.ffi_lib.lib_name = opts.lib_name.clone();
112        module.ffi_lib.lazy = opts.lazy;
113
114        module.unwrap(items);
115        module.user_defs.dedup(&module.class_defs);
116        module
117    }
118
119    fn unwrap(&mut self, items: Vec<Item>) {
120        let mut classes = Vec::new();
121        let mut methods = Vec::new();
122        let mut fns = Vec::new();
123        let mut snippets = Vec::new();
124        let mut helpers = Vec::new();
125        for item in items {
126            match item {
127                Item::Fn(item_fn) => fns.push(item_fn),
128                Item::Method(item_method) => methods.push(item_method),
129                Item::Class(item_class) => classes.push(item_class),
130                Item::Ts(item_ts) => snippets.push(item_ts),
131                Item::Helper(item_helper) => helpers.push(item_helper),
132            }
133        }
134
135        let kept = kept_classes(&classes, &methods, &fns);
136        let foreign: HashSet<String> = classes
137            .iter()
138            .filter(|class| is_foreign(class))
139            .map(|class| class.name.clone())
140            .collect();
141        let is_kept = |class: &str| !foreign.contains(class) || kept.contains(class);
142
143        for ItemClass {
144            name,
145            drop_symbol,
146            ty,
147            ..
148        } in classes.into_iter().filter(|class| is_kept(&class.name))
149        {
150            let class = format_ident!("{}", name);
151            let drop_symbol = format_ident!("{}", drop_symbol);
152            self.ffi_lib.interface.push_fn(FfiFunction {
153                ident: drop_symbol.clone(),
154                inputs: vec![FfiType::Pointer],
155                output: FfiType::Void,
156                non_blocking: false,
157            });
158            self.class_types.insert(ty, class.clone());
159            self.class_defs.store.entry(class).or_default().drop_symbol = Some(drop_symbol);
160        }
161
162        for item_fn in fns {
163            let method = item_fn.unwrap(self, None);
164            self.functions.push(method);
165        }
166
167        for ItemMethod {
168            class,
169            receiver,
170            func,
171        } in methods.into_iter().filter(|method| is_kept(&method.class))
172        {
173            let class = format_ident!("{}", class);
174            let method = func.unwrap(self, Some((class.clone(), receiver)));
175            self.class_defs
176                .store
177                .entry(class)
178                .or_default()
179                .methods
180                .push(method);
181        }
182
183        for ItemTs { class, code } in snippets
184            .into_iter()
185            .filter(|snippet| is_kept(&snippet.class))
186        {
187            self.class_defs
188                .store
189                .entry(format_ident!("{}", class))
190                .or_default()
191                .snippets
192                .push(code);
193        }
194
195        self.add_helpers(helpers);
196    }
197
198    fn add_helpers(&mut self, helpers: Vec<ItemHelper>) {
199        let mut seen = HashSet::new();
200        for ItemHelper {
201            id,
202            runtime,
203            code,
204            symbols,
205        } in helpers
206        {
207            let used = runtime || id.is_empty() || self.used_helpers.contains(&id);
208            if !used || (!id.is_empty() && !seen.insert(id)) {
209                continue;
210            }
211            for symbol in symbols {
212                let ident = format_ident!("{}", symbol.name);
213                let exists = self.ffi_lib.interface.symbols.iter().any(|existing| {
214                    let FfiSymbol::Function(existing) = existing;
215                    existing.ident == ident
216                });
217                if !exists {
218                    self.ffi_lib.interface.push_fn(FfiFunction {
219                        ident,
220                        inputs: symbol.inputs.into_iter().map(FfiType::from).collect(),
221                        output: FfiType::from(symbol.output),
222                        non_blocking: false,
223                    });
224                }
225            }
226            let target = if runtime {
227                &mut self.runtime
228            } else {
229                &mut self.helpers
230            };
231            target.push('\n');
232            target.push_str(&code);
233        }
234
235        for id in &self.used_helpers {
236            if !seen.contains(id) {
237                eprintln!("warning: TypeScript helper `{id}` is used but no crate defines it");
238            }
239        }
240    }
241
242    fn helpers(&self) -> String {
243        let mut helpers = self.helpers.clone();
244        if self.uses_pin {
245            helpers.push_str(PIN_HELPERS);
246        }
247        helpers
248    }
249
250    /// Generate a single typescript module with rust type definitions inlined
251    pub fn generate_single(self, _opts: &CodegenOpts) -> String {
252        let helpers = self.helpers();
253        let TsModule {
254            ffi_lib,
255            user_defs,
256            functions,
257            class_defs,
258            runtime: type_defs,
259            ..
260        } = self;
261
262        let ffi_lib = ffi_lib.to_token_stream().to_string();
263        let user_defs = user_defs.to_token_stream().to_string();
264        let functions = functions.to_token_stream().to_string();
265        let class_defs = class_defs.print();
266
267        expand_raw(format!(
268            "// deno-lint-ignore-file\n
269            {ffi_lib}
270            {type_defs}
271            {helpers}
272            {user_defs}
273            {class_defs}
274            {functions}
275            "
276        ))
277    }
278
279    /// Generate multiple typescript modules with rust type definitions on a
280    /// separate file
281    pub fn generate_multiple(self, _opts: &CodegenOpts, type_defs_name: &str) -> (String, String) {
282        let helpers = self.helpers();
283        let TsModule {
284            mut ffi_lib,
285            type_defs,
286            user_defs,
287            functions,
288            class_defs,
289            runtime,
290            ..
291        } = self;
292        ffi_lib.export = true;
293
294        let mut type_defs_builder = String::from("// deno-lint-ignore-file\n");
295        type_defs_builder.push_str(&runtime);
296        let (type_defs, imports) = type_defs.print_separate(type_defs_builder, type_defs_name);
297
298        let ffi_lib = ffi_lib.to_token_stream().to_string();
299        let user_defs = user_defs.to_token_stream().to_string();
300        let functions = functions.to_token_stream().to_string();
301        let class_defs = class_defs.print();
302
303        let module = expand_raw(format!(
304            "
305            {imports}
306            {ffi_lib}
307            {helpers}
308            {user_defs}
309            {class_defs}
310            {functions}
311            "
312        ));
313
314        (module, type_defs)
315    }
316}