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#[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, pub type_defs: RustTypeDefs, 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
39fn 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 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 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}