1use std::collections::{HashMap, HashSet};
9
10use camino::Utf8Path;
11use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
12use serde::{Deserialize, Serialize};
13use weaveffi_core::abi;
14use weaveffi_core::backend::{LanguageBackend, OutputFile};
15use weaveffi_core::capabilities::TargetCapabilities;
16use weaveffi_core::codegen::common::{emit_doc as common_emit_doc, DocCommentStyle};
17use weaveffi_core::model::{
18 BindingModel, CallbackBinding, EnumBinding, FnBinding, ListenerBinding, ParamBinding,
19 StructBinding,
20};
21use weaveffi_core::pkg::{self, ResolvedPackage};
22use weaveffi_core::utils::{
23 c_abi_struct_name, local_type_name, render_json_prelude, render_prelude, render_trailer,
24 wrapper_name, CommentStyle,
25};
26use weaveffi_ir::ir::{Api, TypeRef};
27
28#[derive(Debug, Clone, Default, Serialize, Deserialize)]
30#[serde(default)]
31pub struct NodeConfig {
32 pub package_name: Option<String>,
34 pub strip_module_prefix: bool,
37 pub prefix: Option<String>,
41 #[serde(skip)]
43 pub input_basename: Option<String>,
44}
45
46impl NodeConfig {
47 pub fn package_name(&self) -> &str {
48 self.package_name.as_deref().unwrap_or("weaveffi")
49 }
50
51 pub fn prefix(&self) -> &str {
52 self.prefix.as_deref().unwrap_or("weaveffi")
53 }
54
55 pub fn input_basename(&self) -> &str {
56 self.input_basename.as_deref().unwrap_or("weaveffi.yml")
57 }
58}
59
60pub struct NodeGenerator;
61
62impl LanguageBackend for NodeGenerator {
63 type Config = NodeConfig;
64
65 fn name(&self) -> &'static str {
66 "node"
67 }
68
69 fn capabilities(&self) -> TargetCapabilities {
70 TargetCapabilities::full()
71 }
72
73 fn prefix<'a>(&self, config: &'a Self::Config) -> &'a str {
74 config.prefix()
75 }
76
77 fn files(
78 &self,
79 api: &Api,
80 _model: &BindingModel,
81 out_dir: &Utf8Path,
82 config: &Self::Config,
83 ) -> Vec<OutputFile> {
84 let dir = out_dir.join("node");
85 let input_basename = config.input_basename();
86 let prefix = config.prefix();
87 let strip = config.strip_module_prefix;
88 vec![
89 OutputFile::new(
90 dir.join("index.js"),
91 render_node_index(api, prefix, strip, input_basename),
92 ),
93 OutputFile::new(
94 dir.join("types.d.ts"),
95 render_node_dts(api, prefix, strip, input_basename),
96 ),
97 OutputFile::new(
98 dir.join("package.json"),
99 render_package_json(
100 &pkg::resolve(
101 api,
102 config.package_name.as_deref(),
103 config.input_basename.as_deref(),
104 ),
105 input_basename,
106 ),
107 ),
108 OutputFile::new(dir.join("binding.gyp"), render_binding_gyp(input_basename)),
109 OutputFile::new(
110 dir.join("weaveffi_addon.c"),
111 render_addon_c(api, prefix, strip, input_basename),
112 ),
113 ]
114 }
115}
116
117weaveffi_core::impl_generator_via_backend!(NodeGenerator);
118
119fn render_package_json(package: &ResolvedPackage, input_basename: &str) -> String {
120 let prelude = render_json_prelude(input_basename);
121 let name = &package.name;
122 let version = &package.version;
123 let description = package.description_or_default();
124 let mut optional = String::new();
125 if let Some(license) = &package.license {
126 optional.push_str(&format!(" \"license\": \"{license}\",\n"));
127 }
128 if let Some(author) = package.authors.first() {
129 optional.push_str(&format!(" \"author\": \"{author}\",\n"));
130 }
131 if let Some(homepage) = &package.homepage {
132 optional.push_str(&format!(" \"homepage\": \"{homepage}\",\n"));
133 }
134 if let Some(repository) = &package.repository {
135 optional.push_str(&format!(
136 " \"repository\": {{ \"type\": \"git\", \"url\": \"{repository}\" }},\n"
137 ));
138 }
139 format!(
140 "{{\n{prelude} \"name\": \"{name}\",\n \"version\": \"{version}\",\n \"description\": \"{description}\",\n{optional} \"main\": \"index.js\",\n \"types\": \"types.d.ts\",\n \"gypfile\": true,\n \"scripts\": {{\n \"install\": \"node-gyp rebuild\"\n }}\n}}\n"
141 )
142}
143
144fn render_binding_gyp(input_basename: &str) -> String {
145 let prelude = render_prelude(CommentStyle::Hash, input_basename);
146 let trailer = render_trailer(CommentStyle::Hash, "binding.gyp");
147 format!(
148 "{prelude}{{\n \"targets\": [\n {{\n \"target_name\": \"weaveffi\",\n \"sources\": [\"weaveffi_addon.c\"],\n \"include_dirs\": [\"../c\"],\n \"libraries\": [\"-lweaveffi\"]\n }}\n ]\n}}\n\n{trailer}"
149 )
150}
151
152fn is_c_ptr_type(ty: &TypeRef) -> bool {
153 matches!(
154 ty,
155 TypeRef::StringUtf8
156 | TypeRef::Bytes
157 | TypeRef::Struct(_)
158 | TypeRef::List(_)
159 | TypeRef::Map(_, _)
160 | TypeRef::Iterator(_)
161 )
162}
163
164fn c_elem_type(ty: &TypeRef, module: &str, prefix: &str) -> String {
165 match ty {
166 TypeRef::I8 => "int8_t".into(),
167 TypeRef::I16 => "int16_t".into(),
168 TypeRef::I32 => "int32_t".into(),
169 TypeRef::I64 => "int64_t".into(),
170 TypeRef::U8 => "uint8_t".into(),
171 TypeRef::U16 => "uint16_t".into(),
172 TypeRef::U32 => "uint32_t".into(),
173 TypeRef::U64 => "uint64_t".into(),
174 TypeRef::F32 => "float".into(),
175 TypeRef::F64 => "double".into(),
176 TypeRef::Bool => "bool".into(),
177 TypeRef::Handle => "weaveffi_handle_t".into(),
181 TypeRef::TypedHandle(s) => format!("{}*", c_abi_struct_name(s, module, prefix)),
182 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "const char*".into(),
183 TypeRef::Bytes | TypeRef::BorrowedBytes => "const uint8_t*".into(),
184 TypeRef::Struct(s) => format!("{}*", c_abi_struct_name(s, module, prefix)),
185 TypeRef::Enum(e) => format!("{prefix}_{module}_{e}"),
186 TypeRef::Optional(inner) | TypeRef::List(inner) | TypeRef::Iterator(inner) => {
187 c_elem_type(inner, module, prefix)
188 }
189 TypeRef::Map(_, _) => "void*".into(),
190 }
191}
192
193fn c_ret_type_str(ty: &TypeRef, module: &str, prefix: &str) -> String {
194 match ty {
195 TypeRef::I8 => "int8_t".into(),
196 TypeRef::I16 => "int16_t".into(),
197 TypeRef::I32 => "int32_t".into(),
198 TypeRef::I64 => "int64_t".into(),
199 TypeRef::U8 => "uint8_t".into(),
200 TypeRef::U16 => "uint16_t".into(),
201 TypeRef::U32 => "uint32_t".into(),
202 TypeRef::U64 => "uint64_t".into(),
203 TypeRef::F32 => "float".into(),
204 TypeRef::F64 => "double".into(),
205 TypeRef::Bool => "bool".into(),
206 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "const char*".into(),
207 TypeRef::Bytes | TypeRef::BorrowedBytes => "const uint8_t*".into(),
208 TypeRef::Handle => "weaveffi_handle_t".into(),
209 TypeRef::TypedHandle(s) => format!("{}*", c_abi_struct_name(s, module, prefix)),
210 TypeRef::Struct(s) => format!("{}*", c_abi_struct_name(s, module, prefix)),
211 TypeRef::Enum(e) => format!("{prefix}_{module}_{e}"),
212 TypeRef::Optional(inner) => {
213 if is_c_ptr_type(inner) {
214 c_ret_type_str(inner, module, prefix)
215 } else {
216 format!("{}*", c_elem_type(inner, module, prefix))
217 }
218 }
219 TypeRef::List(inner) => format!("{}*", c_elem_type(inner, module, prefix)),
220 TypeRef::Map(_, _) => "void".into(),
221 TypeRef::Iterator(_) => "void*".into(),
222 }
223}
224
225fn napi_getter(ty: &TypeRef) -> &'static str {
226 match ty {
227 TypeRef::I8 | TypeRef::I16 | TypeRef::I32 | TypeRef::Enum(_) => "napi_get_value_int32",
230 TypeRef::U8 | TypeRef::U16 | TypeRef::U32 => "napi_get_value_uint32",
231 TypeRef::I64
233 | TypeRef::U64
234 | TypeRef::Handle
235 | TypeRef::TypedHandle(_)
236 | TypeRef::Struct(_) => "napi_get_value_int64",
237 TypeRef::F32 | TypeRef::F64 => "napi_get_value_double",
239 TypeRef::Bool => "napi_get_value_bool",
240 _ => "napi_get_value_int64",
241 }
242}
243
244fn napi_read_tmp_type(ty: &TypeRef) -> &'static str {
250 match ty {
251 TypeRef::I8 | TypeRef::I16 => "int32_t",
252 TypeRef::U8 | TypeRef::U16 => "uint32_t",
253 TypeRef::U64 => "int64_t",
254 TypeRef::F32 => "double",
255 _ => "int64_t",
256 }
257}
258
259fn needs_narrowing_read(ty: &TypeRef) -> bool {
262 matches!(
263 ty,
264 TypeRef::I8 | TypeRef::I16 | TypeRef::U8 | TypeRef::U16 | TypeRef::U64 | TypeRef::F32
265 )
266}
267
268fn render_addon_c(
269 api: &Api,
270 prefix: &str,
271 strip_module_prefix: bool,
272 input_basename: &str,
273) -> String {
274 let mut out = render_prelude(CommentStyle::DoubleSlash, input_basename);
275 out.push_str(&format!(
276 "#include <node_api.h>\n#include \"{prefix}.h\"\n#include <stdlib.h>\n#include <string.h>\n\n"
277 ));
278
279 let model = BindingModel::build(api, prefix);
280 let mut all_exports: Vec<(String, String)> = Vec::new();
281 let structs = struct_registry(&model);
282
283 let has_listeners = model.modules.iter().any(|m| !m.listeners.is_empty());
284 if has_listeners {
285 render_listener_support_c(&mut out, prefix);
286 }
287
288 for m in &model.modules {
289 for e in &m.enums {
294 if e.is_rich() {
295 render_rich_enum_napi_fns(
296 &mut out,
297 e,
298 &m.path,
299 prefix,
300 strip_module_prefix,
301 &structs,
302 &mut all_exports,
303 );
304 }
305 }
306 let used_callbacks: Vec<&CallbackBinding> = m
309 .listeners
310 .iter()
311 .filter_map(|l| m.callback(&l.event_callback))
312 .collect();
313 for cb in &used_callbacks {
314 render_cb_payload_struct(&mut out, cb, prefix);
315 render_cb_tramp(&mut out, cb, prefix);
316 render_cb_calljs(&mut out, cb, prefix);
317 }
318 for l in &m.listeners {
319 let Some(cb) = m.callback(&l.event_callback) else {
320 unreachable!("validation guarantees the listener's callback exists");
321 };
322 render_listener_napi_fns(&mut out, l, cb, prefix);
323 all_exports.push((
324 wrapper_name(
325 &m.path,
326 &format!("register_{}", l.name),
327 strip_module_prefix,
328 ),
329 format!("Napi_{}", l.register_symbol),
330 ));
331 all_exports.push((
332 wrapper_name(
333 &m.path,
334 &format!("unregister_{}", l.name),
335 strip_module_prefix,
336 ),
337 format!("Napi_{}", l.unregister_symbol),
338 ));
339 }
340 for f in &m.functions {
341 let c_name = &f.c_base;
342 let napi_name = format!("Napi_{c_name}");
343 let js_name = wrapper_name(&m.path, &f.name, strip_module_prefix);
344 all_exports.push((js_name, napi_name.clone()));
345
346 if f.is_async {
347 render_async_machinery(&mut out, f, c_name, &m.path, prefix, &structs);
348 }
349
350 out.push_str(&format!(
351 "static napi_value {napi_name}(napi_env env, napi_callback_info info) {{\n"
352 ));
353 if f.is_async {
354 render_async_napi_body(&mut out, f, c_name, &m.path, prefix);
355 } else {
356 render_napi_body(&mut out, f, c_name, &m.path, prefix, &structs);
357 }
358 out.push_str("}\n\n");
359 }
360 }
361
362 out.push_str("static napi_value Init(napi_env env, napi_value exports) {\n");
363 if !all_exports.is_empty() {
364 out.push_str(" napi_property_descriptor props[] = {\n");
365 for (js_name, napi_fn) in &all_exports {
366 out.push_str(&format!(
367 " {{ \"{js_name}\", NULL, {napi_fn}, NULL, NULL, NULL, napi_default, NULL }},\n"
368 ));
369 }
370 out.push_str(" };\n");
371 out.push_str(&format!(
372 " napi_define_properties(env, exports, {}, props);\n",
373 all_exports.len()
374 ));
375 }
376 out.push_str(" return exports;\n");
377 out.push_str("}\n\n");
378 out.push_str("NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)\n\n");
379 out.push_str(&render_trailer(
380 CommentStyle::DoubleSlash,
381 "weaveffi_addon.c",
382 ));
383 out
384}
385
386fn rich_tag_base(enum_name: &str) -> String {
395 format!("{enum_name}_tag")
396}
397
398fn rich_ctor_base(enum_name: &str, variant: &str) -> String {
400 format!("{enum_name}_{}_new", variant.to_snake_case())
401}
402
403fn rich_getter_base(enum_name: &str, variant: &str, field: &str) -> String {
405 format!("{enum_name}_{}_get_{field}", variant.to_snake_case())
406}
407
408fn rich_destroy_base(enum_name: &str) -> String {
410 format!("{enum_name}_destroy")
411}
412
413fn emit_rich_self_read(out: &mut String, c_tag: &str) {
416 out.push_str(" size_t argc = 1;\n");
417 out.push_str(" napi_value args[1];\n");
418 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
419 out.push_str(" int64_t self_raw;\n");
420 out.push_str(" napi_get_value_int64(env, args[0], &self_raw);\n");
421 out.push_str(&format!(
422 " {c_tag}* self = ({c_tag}*)(intptr_t)self_raw;\n"
423 ));
424}
425
426#[allow(clippy::too_many_arguments)]
433fn render_rich_enum_napi_fns(
434 out: &mut String,
435 e: &EnumBinding,
436 module: &str,
437 prefix: &str,
438 strip: bool,
439 structs: &HashMap<String, StructBinding>,
440 all_exports: &mut Vec<(String, String)>,
441) {
442 let Some(rich) = &e.rich else {
443 return;
444 };
445 let c_tag = &e.c_tag;
446 let name = &e.name;
447
448 let napi_tag = format!("Napi_{}", rich.tag_symbol);
450 out.push_str(&format!(
451 "static napi_value {napi_tag}(napi_env env, napi_callback_info info) {{\n"
452 ));
453 emit_rich_self_read(out, c_tag);
454 out.push_str(" napi_value ret;\n");
455 out.push_str(&format!(
456 " napi_create_int32(env, {}(self), &ret);\n",
457 rich.tag_symbol
458 ));
459 out.push_str(" return ret;\n}\n\n");
460 all_exports.push((wrapper_name(module, &rich_tag_base(name), strip), napi_tag));
461
462 for v in &rich.variants {
466 let napi_ctor = format!("Napi_{}", v.create.symbol);
467 out.push_str(&format!(
468 "static napi_value {napi_ctor}(napi_env env, napi_callback_info info) {{\n"
469 ));
470 let n = v.fields.len();
471 if n > 0 {
472 out.push_str(&format!(" size_t argc = {n};\n"));
473 out.push_str(&format!(" napi_value args[{n}];\n"));
474 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
475 } else {
476 out.push_str(" size_t argc = 0;\n");
477 out.push_str(" napi_get_cb_info(env, info, &argc, NULL, NULL, NULL);\n");
478 }
479 let mut c_args: Vec<String> = Vec::new();
480 let mut cleanups: Vec<String> = Vec::new();
481 for (i, f) in v.fields.iter().enumerate() {
482 emit_param(
483 out,
484 &mut c_args,
485 &mut cleanups,
486 &f.ty,
487 &f.name,
488 i,
489 module,
490 prefix,
491 );
492 }
493 out.push_str(" weaveffi_error err = {0};\n");
494 c_args.push("&err".to_string());
495 out.push_str(&format!(
496 " {c_tag}* result = {}({});\n",
497 v.create.symbol,
498 c_args.join(", ")
499 ));
500 for cleanup in &cleanups {
501 out.push_str(cleanup);
502 }
503 out.push_str(" if (err.code != 0) {\n");
504 out.push_str(" napi_throw_error(env, NULL, err.message);\n");
505 out.push_str(" weaveffi_error_clear(&err);\n");
506 out.push_str(" return NULL;\n");
507 out.push_str(" }\n");
508 out.push_str(" napi_value ret;\n");
509 out.push_str(" napi_create_int64(env, (int64_t)(intptr_t)result, &ret);\n");
510 out.push_str(" return ret;\n}\n\n");
511 all_exports.push((
512 wrapper_name(module, &rich_ctor_base(name, &v.name), strip),
513 napi_ctor,
514 ));
515 }
516
517 for v in &rich.variants {
521 for f in &v.fields {
522 let napi_getter = format!("Napi_{}", f.getter_symbol);
523 out.push_str(&format!(
524 "static napi_value {napi_getter}(napi_env env, napi_callback_info info) {{\n"
525 ));
526 emit_rich_self_read(out, c_tag);
527 out.push_str(" napi_value ret;\n");
528 emit_struct_field_to_napi(
529 out,
530 "env",
531 &f.ty,
532 &f.getter_symbol,
533 "self",
534 "ret",
535 module,
536 prefix,
537 structs,
538 " ",
539 );
540 out.push_str(" return ret;\n}\n\n");
541 all_exports.push((
542 wrapper_name(module, &rich_getter_base(name, &v.name, &f.name), strip),
543 napi_getter,
544 ));
545 }
546 }
547
548 let napi_destroy = format!("Napi_{}", rich.destroy_symbol);
550 out.push_str(&format!(
551 "static napi_value {napi_destroy}(napi_env env, napi_callback_info info) {{\n"
552 ));
553 emit_rich_self_read(out, c_tag);
554 out.push_str(&format!(" {}(self);\n", rich.destroy_symbol));
555 out.push_str(" napi_value ret;\n");
556 out.push_str(" napi_get_undefined(env, &ret);\n");
557 out.push_str(" return ret;\n}\n\n");
558 all_exports.push((
559 wrapper_name(module, &rich_destroy_base(name), strip),
560 napi_destroy,
561 ));
562}
563
564fn render_listener_support_c(out: &mut String, prefix: &str) {
568 out.push_str(&format!("typedef struct {prefix}_napi_listener_ctx {{\n"));
569 out.push_str(" napi_threadsafe_function tsfn;\n");
570 out.push_str(" uint64_t id;\n");
571 out.push_str(&format!(" struct {prefix}_napi_listener_ctx* next;\n"));
572 out.push_str(&format!("}} {prefix}_napi_listener_ctx;\n\n"));
573 out.push_str(&format!(
574 "static {prefix}_napi_listener_ctx* {prefix}_napi_listeners = NULL;\n\n"
575 ));
576}
577
578fn cb_payload_name(cb: &CallbackBinding) -> String {
579 format!("{}_payload", cb.c_fn_type)
580}
581
582fn cb_slot_decls(cb: &CallbackBinding, prefix: &str) -> Vec<String> {
584 cb.params
585 .iter()
586 .flat_map(|p| abi::lower_param(&p.name, &p.ty, "", false))
587 .map(|slot| format!("{} {}", slot.ty.render_c(prefix), slot.name))
588 .collect()
589}
590
591fn render_cb_payload_struct(out: &mut String, cb: &CallbackBinding, prefix: &str) {
596 out.push_str("typedef struct {\n");
597 for p in &cb.params {
598 let slots = abi::lower_param(&p.name, &p.ty, "", false);
599 let n0 = &slots[0].name;
600 match &p.ty {
601 TypeRef::I8
602 | TypeRef::I16
603 | TypeRef::I32
604 | TypeRef::I64
605 | TypeRef::U8
606 | TypeRef::U16
607 | TypeRef::U32
608 | TypeRef::U64
609 | TypeRef::F32
610 | TypeRef::F64
611 | TypeRef::Bool
612 | TypeRef::Handle
613 | TypeRef::Enum(_) => {
614 out.push_str(&format!(" {} {n0};\n", slots[0].ty.render_c(prefix)));
615 }
616 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
617 out.push_str(&format!(" char* {n0};\n"));
618 }
619 TypeRef::Bytes | TypeRef::BorrowedBytes => {
620 out.push_str(&format!(" uint8_t* {n0};\n"));
621 out.push_str(&format!(" size_t {};\n", slots[1].name));
622 }
623 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
624 out.push_str(&format!(" void* {n0};\n"));
625 }
626 TypeRef::Optional(inner) => match inner.as_ref() {
627 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
628 out.push_str(&format!(" char* {n0};\n"));
629 }
630 TypeRef::Bytes | TypeRef::BorrowedBytes => {
631 out.push_str(&format!(" int {n0}_has;\n"));
632 out.push_str(&format!(" uint8_t* {n0};\n"));
633 out.push_str(&format!(" size_t {};\n", slots[1].name));
634 }
635 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
636 out.push_str(&format!(" void* {n0};\n"));
637 }
638 other => {
639 out.push_str(&format!(" int {n0}_has;\n"));
640 out.push_str(&format!(
641 " {} {n0};\n",
642 abi::element_ctype(other, "").render_c(prefix)
643 ));
644 }
645 },
646 TypeRef::List(inner) => {
647 let elem = elem_payload_ctype(inner, prefix);
648 out.push_str(&format!(" {elem}* {n0};\n"));
649 out.push_str(&format!(" size_t {};\n", slots[1].name));
650 }
651 TypeRef::Map(k, v) => {
652 let kt = elem_payload_ctype(k, prefix);
653 let vt = elem_payload_ctype(v, prefix);
654 out.push_str(&format!(" {kt}* {n0};\n"));
655 out.push_str(&format!(" {vt}* {};\n", slots[1].name));
656 out.push_str(&format!(" size_t {};\n", slots[2].name));
657 }
658 TypeRef::Iterator(_) => unreachable!("validated: iterator not a callback param"),
659 }
660 }
661 out.push_str(&format!("}} {};\n\n", cb_payload_name(cb)));
662}
663
664fn elem_payload_ctype(ty: &TypeRef, prefix: &str) -> String {
667 match ty {
668 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "char*".into(),
669 other => abi::element_ctype(other, "").render_c(prefix),
670 }
671}
672
673fn render_cb_tramp(out: &mut String, cb: &CallbackBinding, prefix: &str) {
677 let payload = cb_payload_name(cb);
678 let mut decls = cb_slot_decls(cb, prefix);
679 decls.push("void* context".into());
680 out.push_str(&format!(
681 "static void {}_napi_tramp({}) {{\n",
682 cb.c_fn_type,
683 decls.join(", ")
684 ));
685 out.push_str(&format!(
686 " {prefix}_napi_listener_ctx* ctx = ({prefix}_napi_listener_ctx*)context;\n"
687 ));
688 out.push_str(&format!(
689 " {payload}* p = ({payload}*)calloc(1, sizeof({payload}));\n"
690 ));
691 for p in &cb.params {
692 let slots = abi::lower_param(&p.name, &p.ty, "", false);
693 let n0 = &slots[0].name;
694 match &p.ty {
695 TypeRef::I8
696 | TypeRef::I16
697 | TypeRef::I32
698 | TypeRef::I64
699 | TypeRef::U8
700 | TypeRef::U16
701 | TypeRef::U32
702 | TypeRef::U64
703 | TypeRef::F32
704 | TypeRef::F64
705 | TypeRef::Bool
706 | TypeRef::Handle
707 | TypeRef::Enum(_) => {
708 out.push_str(&format!(" p->{n0} = {n0};\n"));
709 }
710 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
711 out.push_str(&format!(" p->{n0} = {n0} ? strdup({n0}) : NULL;\n"));
712 }
713 TypeRef::Bytes | TypeRef::BorrowedBytes => {
714 let n1 = &slots[1].name;
715 out.push_str(&format!(" p->{n1} = {n1};\n"));
716 out.push_str(&format!(
717 " if ({n0} != NULL && {n1} > 0) {{ p->{n0} = (uint8_t*)malloc({n1}); memcpy(p->{n0}, {n0}, {n1}); }}\n"
718 ));
719 }
720 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
721 out.push_str(&format!(" p->{n0} = (void*){n0};\n"));
722 }
723 TypeRef::Optional(inner) => match inner.as_ref() {
724 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
725 out.push_str(&format!(" p->{n0} = {n0} ? strdup({n0}) : NULL;\n"));
726 }
727 TypeRef::Bytes | TypeRef::BorrowedBytes => {
728 let n1 = &slots[1].name;
729 out.push_str(&format!(" p->{n0}_has = {n0} != NULL;\n"));
730 out.push_str(&format!(" p->{n1} = {n1};\n"));
731 out.push_str(&format!(
732 " if ({n0} != NULL && {n1} > 0) {{ p->{n0} = (uint8_t*)malloc({n1}); memcpy(p->{n0}, {n0}, {n1}); }}\n"
733 ));
734 }
735 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
736 out.push_str(&format!(" p->{n0} = (void*){n0};\n"));
737 }
738 _ => {
739 out.push_str(&format!(" p->{n0}_has = {n0} != NULL;\n"));
740 out.push_str(&format!(" if ({n0} != NULL) p->{n0} = *{n0};\n"));
741 }
742 },
743 TypeRef::List(inner) => {
744 let n1 = &slots[1].name;
745 out.push_str(&format!(" p->{n1} = {n1};\n"));
746 match inner.as_ref() {
747 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
748 out.push_str(&format!(
749 " if ({n0} != NULL && {n1} > 0) {{\n p->{n0} = (char**)calloc({n1}, sizeof(char*));\n for (size_t i = 0; i < {n1}; i++) p->{n0}[i] = {n0}[i] ? strdup({n0}[i]) : NULL;\n }}\n"
750 ));
751 }
752 _ => {
753 out.push_str(&format!(
754 " if ({n0} != NULL && {n1} > 0) {{ p->{n0} = malloc({n1} * sizeof(*p->{n0})); memcpy(p->{n0}, {n0}, {n1} * sizeof(*p->{n0})); }}\n"
755 ));
756 }
757 }
758 }
759 TypeRef::Map(k, v) => {
760 let keys = n0;
761 let vals = &slots[1].name;
762 let len = &slots[2].name;
763 out.push_str(&format!(" p->{len} = {len};\n"));
764 for (base, ty) in [(keys, k), (vals, v)] {
765 match ty.as_ref() {
766 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
767 out.push_str(&format!(
768 " if ({base} != NULL && {len} > 0) {{\n p->{base} = (char**)calloc({len}, sizeof(char*));\n for (size_t i = 0; i < {len}; i++) p->{base}[i] = {base}[i] ? strdup({base}[i]) : NULL;\n }}\n"
769 ));
770 }
771 _ => {
772 out.push_str(&format!(
773 " if ({base} != NULL && {len} > 0) {{ p->{base} = malloc({len} * sizeof(*p->{base})); memcpy(p->{base}, {base}, {len} * sizeof(*p->{base})); }}\n"
774 ));
775 }
776 }
777 }
778 }
779 TypeRef::Iterator(_) => unreachable!("validated: iterator not a callback param"),
780 }
781 }
782 out.push_str(" napi_call_threadsafe_function(ctx->tsfn, p, napi_tsfn_nonblocking);\n");
783 out.push_str("}\n\n");
784}
785
786fn emit_payload_to_napi(out: &mut String, p: &ParamBinding, idx: usize, prefix: &str) {
788 let slots = abi::lower_param(&p.name, &p.ty, "", false);
789 let n0 = &slots[0].name;
790 let target = format!("argv[{idx}]");
791 let _ = prefix;
792 match &p.ty {
793 TypeRef::I32 => out.push_str(&format!(
794 " napi_create_int32(env, p->{n0}, &{target});\n"
795 )),
796 TypeRef::U32 => out.push_str(&format!(
797 " napi_create_uint32(env, p->{n0}, &{target});\n"
798 )),
799 TypeRef::I64 => out.push_str(&format!(
800 " napi_create_int64(env, p->{n0}, &{target});\n"
801 )),
802 TypeRef::F64 => out.push_str(&format!(
803 " napi_create_double(env, p->{n0}, &{target});\n"
804 )),
805 TypeRef::I8 | TypeRef::I16 => out.push_str(&format!(
806 " napi_create_int32(env, p->{n0}, &{target});\n"
807 )),
808 TypeRef::U8 | TypeRef::U16 => out.push_str(&format!(
809 " napi_create_uint32(env, p->{n0}, &{target});\n"
810 )),
811 TypeRef::U64 => out.push_str(&format!(
812 " napi_create_int64(env, (int64_t)p->{n0}, &{target});\n"
813 )),
814 TypeRef::F32 => out.push_str(&format!(
815 " napi_create_double(env, p->{n0}, &{target});\n"
816 )),
817 TypeRef::Bool => out.push_str(&format!(
818 " napi_get_boolean(env, p->{n0}, &{target});\n"
819 )),
820 TypeRef::Handle => out.push_str(&format!(
821 " napi_create_int64(env, (int64_t)p->{n0}, &{target});\n"
822 )),
823 TypeRef::Enum(_) => out.push_str(&format!(
824 " napi_create_int32(env, (int32_t)p->{n0}, &{target});\n"
825 )),
826 TypeRef::StringUtf8 | TypeRef::BorrowedStr => out.push_str(&format!(
827 " napi_create_string_utf8(env, p->{n0} ? p->{n0} : \"\", NAPI_AUTO_LENGTH, &{target});\n"
828 )),
829 TypeRef::Bytes | TypeRef::BorrowedBytes => {
830 let n1 = &slots[1].name;
831 out.push_str(&format!(
832 " napi_create_buffer_copy(env, p->{n1}, p->{n0} ? (const void*)p->{n0} : (const void*)\"\", NULL, &{target});\n"
833 ));
834 }
835 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => out.push_str(&format!(
836 " napi_create_int64(env, (int64_t)(intptr_t)p->{n0}, &{target});\n"
837 )),
838 TypeRef::Optional(inner) => match inner.as_ref() {
839 TypeRef::StringUtf8 | TypeRef::BorrowedStr => out.push_str(&format!(
840 " if (p->{n0}) napi_create_string_utf8(env, p->{n0}, NAPI_AUTO_LENGTH, &{target}); else napi_get_null(env, &{target});\n"
841 )),
842 TypeRef::Bytes | TypeRef::BorrowedBytes => {
843 let n1 = &slots[1].name;
844 out.push_str(&format!(
845 " if (p->{n0}_has) napi_create_buffer_copy(env, p->{n1}, p->{n0} ? (const void*)p->{n0} : (const void*)\"\", NULL, &{target}); else napi_get_null(env, &{target});\n"
846 ));
847 }
848 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => out.push_str(&format!(
849 " if (p->{n0}) napi_create_int64(env, (int64_t)(intptr_t)p->{n0}, &{target}); else napi_get_null(env, &{target});\n"
850 )),
851 other => {
852 let leaf = payload_leaf_to_napi(other, &format!("p->{n0}"), &target);
853 out.push_str(&format!(
854 " if (p->{n0}_has) {{ {leaf} }} else napi_get_null(env, &{target});\n"
855 ));
856 }
857 },
858 TypeRef::List(inner) => {
859 let n1 = &slots[1].name;
860 out.push_str(&format!(" napi_create_array(env, &{target});\n"));
861 out.push_str(&format!(
862 " for (size_t i = 0; p->{n0} != NULL && i < p->{n1}; i++) {{\n"
863 ));
864 out.push_str(" napi_value elem;\n");
865 let leaf = payload_elem_to_napi(inner, &format!("p->{n0}[i]"), "elem");
866 out.push_str(&format!(" {leaf}\n"));
867 out.push_str(&format!(
868 " napi_set_element(env, {target}, (uint32_t)i, elem);\n"
869 ));
870 out.push_str(" }\n");
871 }
872 TypeRef::Map(k, v) => {
873 let keys = n0;
874 let vals = &slots[1].name;
875 let len = &slots[2].name;
876 out.push_str(&format!(" napi_create_object(env, &{target});\n"));
877 out.push_str(&format!(
878 " for (size_t i = 0; p->{keys} != NULL && p->{vals} != NULL && i < p->{len}; i++) {{\n"
879 ));
880 out.push_str(" napi_value mk; napi_value mv;\n");
881 let kc = payload_elem_to_napi(k, &format!("p->{keys}[i]"), "mk");
882 let vc = payload_elem_to_napi(v, &format!("p->{vals}[i]"), "mv");
883 out.push_str(&format!(" {kc}\n"));
884 out.push_str(&format!(" {vc}\n"));
885 out.push_str(&format!(
886 " napi_set_property(env, {target}, mk, mv);\n"
887 ));
888 out.push_str(" }\n");
889 }
890 TypeRef::Iterator(_) => unreachable!("validated: iterator not a callback param"),
891 }
892}
893
894fn payload_leaf_to_napi(ty: &TypeRef, expr: &str, target: &str) -> String {
896 match ty {
897 TypeRef::I32 => format!("napi_create_int32(env, {expr}, &{target});"),
898 TypeRef::U32 => format!("napi_create_uint32(env, {expr}, &{target});"),
899 TypeRef::I64 => format!("napi_create_int64(env, {expr}, &{target});"),
900 TypeRef::F64 => format!("napi_create_double(env, {expr}, &{target});"),
901 TypeRef::I8 | TypeRef::I16 => format!("napi_create_int32(env, {expr}, &{target});"),
902 TypeRef::U8 | TypeRef::U16 => format!("napi_create_uint32(env, {expr}, &{target});"),
903 TypeRef::U64 => format!("napi_create_int64(env, (int64_t){expr}, &{target});"),
904 TypeRef::F32 => format!("napi_create_double(env, {expr}, &{target});"),
905 TypeRef::Bool => format!("napi_get_boolean(env, {expr}, &{target});"),
906 TypeRef::Handle => format!("napi_create_int64(env, (int64_t){expr}, &{target});"),
907 TypeRef::Enum(_) => format!("napi_create_int32(env, (int32_t){expr}, &{target});"),
908 _ => format!("napi_get_null(env, &{target});"),
909 }
910}
911
912fn payload_elem_to_napi(ty: &TypeRef, expr: &str, target: &str) -> String {
914 match ty {
915 TypeRef::StringUtf8 | TypeRef::BorrowedStr => format!(
916 "napi_create_string_utf8(env, {expr} ? {expr} : \"\", NAPI_AUTO_LENGTH, &{target});"
917 ),
918 other => payload_leaf_to_napi(other, expr, target),
919 }
920}
921
922fn emit_payload_free(out: &mut String, p: &ParamBinding) {
924 let slots = abi::lower_param(&p.name, &p.ty, "", false);
925 let n0 = &slots[0].name;
926 match &p.ty {
927 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
928 out.push_str(&format!(" free(p->{n0});\n"));
929 }
930 TypeRef::Bytes | TypeRef::BorrowedBytes => {
931 out.push_str(&format!(" free(p->{n0});\n"));
932 }
933 TypeRef::Optional(inner) => match inner.as_ref() {
934 TypeRef::StringUtf8
935 | TypeRef::BorrowedStr
936 | TypeRef::Bytes
937 | TypeRef::BorrowedBytes => {
938 out.push_str(&format!(" free(p->{n0});\n"));
939 }
940 _ => {}
941 },
942 TypeRef::List(inner) => {
943 let n1 = &slots[1].name;
944 if matches!(inner.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) {
945 out.push_str(&format!(
946 " for (size_t i = 0; p->{n0} != NULL && i < p->{n1}; i++) free(p->{n0}[i]);\n"
947 ));
948 }
949 out.push_str(&format!(" free(p->{n0});\n"));
950 }
951 TypeRef::Map(k, v) => {
952 let keys = n0;
953 let vals = &slots[1].name;
954 let len = &slots[2].name;
955 for (base, ty) in [(keys, k), (vals, v)] {
956 if matches!(ty.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) {
957 out.push_str(&format!(
958 " for (size_t i = 0; p->{base} != NULL && i < p->{len}; i++) free(p->{base}[i]);\n"
959 ));
960 }
961 out.push_str(&format!(" free(p->{base});\n"));
962 }
963 }
964 _ => {}
965 }
966}
967
968fn render_cb_calljs(out: &mut String, cb: &CallbackBinding, prefix: &str) {
971 let payload = cb_payload_name(cb);
972 out.push_str(&format!(
973 "static void {}_napi_calljs(napi_env env, napi_value js_cb, void* context, void* data) {{\n",
974 cb.c_fn_type
975 ));
976 out.push_str(" (void)context;\n");
977 out.push_str(&format!(" {payload}* p = ({payload}*)data;\n"));
978 out.push_str(" if (env != NULL) {\n");
979 out.push_str(" napi_value undefined;\n");
980 out.push_str(" napi_get_undefined(env, &undefined);\n");
981 let argc = cb.params.len();
982 if argc > 0 {
983 out.push_str(&format!(" napi_value argv[{argc}];\n"));
984 for (i, p) in cb.params.iter().enumerate() {
985 emit_payload_to_napi(out, p, i, prefix);
986 }
987 out.push_str(&format!(
988 " napi_call_function(env, undefined, js_cb, {argc}, argv, NULL);\n"
989 ));
990 } else {
991 out.push_str(" napi_call_function(env, undefined, js_cb, 0, NULL, NULL);\n");
992 }
993 out.push_str(" }\n");
994 for p in &cb.params {
995 emit_payload_free(out, p);
996 }
997 out.push_str(" free(p);\n");
998 out.push_str("}\n\n");
999}
1000
1001fn render_listener_napi_fns(
1006 out: &mut String,
1007 l: &ListenerBinding,
1008 cb: &CallbackBinding,
1009 prefix: &str,
1010) {
1011 let register_sym = &l.register_symbol;
1012 let unregister_sym = &l.unregister_symbol;
1013 let tramp = format!("{}_napi_tramp", cb.c_fn_type);
1014 let calljs = format!("{}_napi_calljs", cb.c_fn_type);
1015
1016 out.push_str(&format!(
1017 "static napi_value Napi_{register_sym}(napi_env env, napi_callback_info info) {{\n"
1018 ));
1019 out.push_str(" size_t argc = 1;\n");
1020 out.push_str(" napi_value args[1];\n");
1021 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
1022 out.push_str(&format!(
1023 " {prefix}_napi_listener_ctx* ctx = ({prefix}_napi_listener_ctx*)calloc(1, sizeof({prefix}_napi_listener_ctx));\n"
1024 ));
1025 out.push_str(" napi_value resource_name;\n");
1026 out.push_str(&format!(
1027 " napi_create_string_utf8(env, \"{register_sym}\", NAPI_AUTO_LENGTH, &resource_name);\n"
1028 ));
1029 out.push_str(&format!(
1030 " napi_create_threadsafe_function(env, args[0], NULL, resource_name, 0, 1, NULL, NULL, NULL, {calljs}, &ctx->tsfn);\n"
1031 ));
1032 out.push_str(" napi_unref_threadsafe_function(env, ctx->tsfn);\n");
1033 out.push_str(&format!(" uint64_t id = {register_sym}({tramp}, ctx);\n"));
1034 out.push_str(" ctx->id = id;\n");
1035 out.push_str(&format!(" ctx->next = {prefix}_napi_listeners;\n"));
1036 out.push_str(&format!(" {prefix}_napi_listeners = ctx;\n"));
1037 out.push_str(" napi_value ret;\n");
1038 out.push_str(" napi_create_double(env, (double)id, &ret);\n");
1039 out.push_str(" return ret;\n");
1040 out.push_str("}\n\n");
1041
1042 out.push_str(&format!(
1043 "static napi_value Napi_{unregister_sym}(napi_env env, napi_callback_info info) {{\n"
1044 ));
1045 out.push_str(" size_t argc = 1;\n");
1046 out.push_str(" napi_value args[1];\n");
1047 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
1048 out.push_str(" double id_d = 0;\n");
1049 out.push_str(" napi_get_value_double(env, args[0], &id_d);\n");
1050 out.push_str(" uint64_t id = (uint64_t)id_d;\n");
1051 out.push_str(&format!(" {unregister_sym}(id);\n"));
1054 out.push_str(&format!(
1055 " {prefix}_napi_listener_ctx** link = &{prefix}_napi_listeners;\n"
1056 ));
1057 out.push_str(" while (*link != NULL) {\n");
1058 out.push_str(" if ((*link)->id == id) {\n");
1059 out.push_str(&format!(
1060 " {prefix}_napi_listener_ctx* found = *link;\n"
1061 ));
1062 out.push_str(" *link = found->next;\n");
1063 out.push_str(" napi_release_threadsafe_function(found->tsfn, napi_tsfn_release);\n");
1064 out.push_str(" free(found);\n");
1065 out.push_str(" break;\n");
1066 out.push_str(" }\n");
1067 out.push_str(" link = &(*link)->next;\n");
1068 out.push_str(" }\n");
1069 out.push_str(" napi_value ret;\n");
1070 out.push_str(" napi_get_undefined(env, &ret);\n");
1071 out.push_str(" return ret;\n");
1072 out.push_str("}\n\n");
1073}
1074
1075fn async_cb_result_params_node(ret: Option<&TypeRef>, module: &str, prefix: &str) -> String {
1076 match ret {
1077 None => String::new(),
1078 Some(TypeRef::StringUtf8 | TypeRef::BorrowedStr) => ", const char* result".into(),
1079 Some(TypeRef::Bytes | TypeRef::BorrowedBytes) => {
1080 ", const uint8_t* result, size_t result_len".into()
1081 }
1082 Some(TypeRef::List(inner)) => {
1083 let et = c_elem_type(inner, module, prefix);
1084 format!(", {et}* result, size_t result_len")
1085 }
1086 Some(TypeRef::Map(k, v)) => {
1087 let kt = c_elem_type(k, module, prefix);
1088 let vt = c_elem_type(v, module, prefix);
1089 format!(", {kt}* result_keys, {vt}* result_values, size_t result_len")
1090 }
1091 Some(t) => format!(", {} result", c_ret_type_str(t, module, prefix)),
1092 }
1093}
1094
1095fn render_async_machinery(
1104 out: &mut String,
1105 f: &FnBinding,
1106 c_name: &str,
1107 module: &str,
1108 prefix: &str,
1109 structs: &HashMap<String, StructBinding>,
1110) {
1111 let actx = format!("{c_name}_napi_actx");
1112 let cb_name = format!("{c_name}_napi_cb");
1113 let calljs = format!("{c_name}_napi_settle");
1114 let cb_result = async_cb_result_params_node(f.ret.as_ref(), module, prefix);
1115
1116 out.push_str("typedef struct {\n");
1118 out.push_str(" napi_deferred deferred;\n");
1119 out.push_str(" napi_threadsafe_function tsfn;\n");
1120 out.push_str(" int32_t err_code;\n");
1121 out.push_str(" char* err_msg;\n");
1122 match f.ret.as_ref() {
1123 None => {}
1124 Some(TypeRef::I32) => out.push_str(" int32_t result;\n"),
1125 Some(TypeRef::U32) => out.push_str(" uint32_t result;\n"),
1126 Some(TypeRef::I64) => out.push_str(" int64_t result;\n"),
1127 Some(TypeRef::F64) => out.push_str(" double result;\n"),
1128 Some(TypeRef::I8) => out.push_str(" int8_t result;\n"),
1129 Some(TypeRef::I16) => out.push_str(" int16_t result;\n"),
1130 Some(TypeRef::U8) => out.push_str(" uint8_t result;\n"),
1131 Some(TypeRef::U16) => out.push_str(" uint16_t result;\n"),
1132 Some(TypeRef::U64) => out.push_str(" uint64_t result;\n"),
1133 Some(TypeRef::F32) => out.push_str(" float result;\n"),
1134 Some(TypeRef::Bool) => out.push_str(" bool result;\n"),
1135 Some(TypeRef::Enum(_)) => out.push_str(" int32_t result;\n"),
1136 Some(TypeRef::StringUtf8 | TypeRef::BorrowedStr) => {
1137 out.push_str(" char* result;\n");
1138 out.push_str(" int result_null;\n");
1139 }
1140 Some(TypeRef::Bytes | TypeRef::BorrowedBytes) => {
1141 out.push_str(" uint8_t* result;\n");
1142 out.push_str(" size_t result_len;\n");
1143 }
1144 Some(TypeRef::Handle) => out.push_str(" uint64_t result;\n"),
1145 Some(TypeRef::TypedHandle(_) | TypeRef::Struct(_) | TypeRef::Iterator(_)) => {
1146 out.push_str(" void* result;\n")
1147 }
1148 Some(TypeRef::Optional(inner)) => match inner.as_ref() {
1149 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1150 out.push_str(" char* result;\n");
1151 out.push_str(" int result_null;\n");
1152 }
1153 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
1154 out.push_str(" void* result;\n");
1155 }
1156 other => {
1157 out.push_str(" int result_has;\n");
1158 out.push_str(&format!(
1159 " {} result;\n",
1160 c_elem_type(other, module, prefix)
1161 ));
1162 }
1163 },
1164 Some(TypeRef::List(inner)) => {
1165 let elem = match inner.as_ref() {
1166 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "char*".to_string(),
1167 other => c_elem_type(other, module, prefix),
1168 };
1169 out.push_str(&format!(" {elem}* result;\n"));
1170 out.push_str(" size_t result_len;\n");
1171 }
1172 Some(TypeRef::Map(k, v)) => {
1173 for (field, ty) in [("result_keys", k), ("result_values", v)] {
1174 let elem = match ty.as_ref() {
1175 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "char*".to_string(),
1176 other => c_elem_type(other, module, prefix),
1177 };
1178 out.push_str(&format!(" {elem}* {field};\n"));
1179 }
1180 out.push_str(" size_t result_len;\n");
1181 }
1182 }
1183 out.push_str(&format!("}} {actx};\n\n"));
1184
1185 out.push_str(&format!(
1187 "static void {cb_name}(void* context, weaveffi_error* err{cb_result}) {{\n"
1188 ));
1189 out.push_str(&format!(" {actx}* ctx = ({actx}*)context;\n"));
1190 out.push_str(" if (err != NULL && err->code != 0) {\n");
1191 out.push_str(" ctx->err_code = err->code;\n");
1192 out.push_str(
1193 " ctx->err_msg = err->message ? strdup(err->message) : strdup(\"unknown error\");\n",
1194 );
1195 out.push_str(" } else {\n");
1196 match f.ret.as_ref() {
1197 None => {}
1198 Some(
1199 TypeRef::I8
1200 | TypeRef::I16
1201 | TypeRef::I32
1202 | TypeRef::I64
1203 | TypeRef::U8
1204 | TypeRef::U16
1205 | TypeRef::U32
1206 | TypeRef::U64
1207 | TypeRef::F32
1208 | TypeRef::F64
1209 | TypeRef::Bool
1210 | TypeRef::Handle,
1211 ) => {
1212 out.push_str(" ctx->result = result;\n");
1213 }
1214 Some(TypeRef::Enum(_)) => {
1215 out.push_str(" ctx->result = (int32_t)result;\n");
1216 }
1217 Some(TypeRef::StringUtf8 | TypeRef::BorrowedStr) => {
1218 out.push_str(" ctx->result_null = result == NULL;\n");
1219 out.push_str(" ctx->result = result ? strdup(result) : NULL;\n");
1220 }
1221 Some(TypeRef::Bytes | TypeRef::BorrowedBytes) => {
1222 out.push_str(" ctx->result_len = result_len;\n");
1223 out.push_str(
1224 " if (result != NULL && result_len > 0) { ctx->result = (uint8_t*)malloc(result_len); memcpy(ctx->result, result, result_len); }\n",
1225 );
1226 }
1227 Some(TypeRef::TypedHandle(_) | TypeRef::Struct(_) | TypeRef::Iterator(_)) => {
1230 out.push_str(" ctx->result = (void*)result;\n");
1231 }
1232 Some(TypeRef::Optional(inner)) => match inner.as_ref() {
1233 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1234 out.push_str(" ctx->result_null = result == NULL;\n");
1235 out.push_str(" ctx->result = result ? strdup(result) : NULL;\n");
1236 }
1237 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
1238 out.push_str(" ctx->result = (void*)result;\n");
1239 }
1240 _ => {
1241 out.push_str(" ctx->result_has = result != NULL;\n");
1242 out.push_str(" if (result != NULL) ctx->result = *result;\n");
1243 }
1244 },
1245 Some(TypeRef::List(inner)) => {
1246 out.push_str(" ctx->result_len = result_len;\n");
1247 match inner.as_ref() {
1248 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1249 out.push_str(
1250 " if (result != NULL && result_len > 0) {\n ctx->result = (char**)calloc(result_len, sizeof(char*));\n for (size_t i = 0; i < result_len; i++) ctx->result[i] = result[i] ? strdup(result[i]) : NULL;\n }\n",
1251 );
1252 }
1253 _ => {
1254 out.push_str(
1255 " if (result != NULL && result_len > 0) { ctx->result = malloc(result_len * sizeof(*ctx->result)); memcpy(ctx->result, result, result_len * sizeof(*ctx->result)); }\n",
1256 );
1257 }
1258 }
1259 }
1260 Some(TypeRef::Map(k, v)) => {
1261 out.push_str(" ctx->result_len = result_len;\n");
1262 for (field, src, ty) in [
1263 ("result_keys", "result_keys", k),
1264 ("result_values", "result_values", v),
1265 ] {
1266 match ty.as_ref() {
1267 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1268 out.push_str(&format!(
1269 " if ({src} != NULL && result_len > 0) {{\n ctx->{field} = (char**)calloc(result_len, sizeof(char*));\n for (size_t i = 0; i < result_len; i++) ctx->{field}[i] = {src}[i] ? strdup({src}[i]) : NULL;\n }}\n"
1270 ));
1271 }
1272 _ => {
1273 out.push_str(&format!(
1274 " if ({src} != NULL && result_len > 0) {{ ctx->{field} = malloc(result_len * sizeof(*ctx->{field})); memcpy(ctx->{field}, {src}, result_len * sizeof(*ctx->{field})); }}\n"
1275 ));
1276 }
1277 }
1278 }
1279 }
1280 }
1281 out.push_str(" }\n");
1282 out.push_str(" napi_call_threadsafe_function(ctx->tsfn, ctx, napi_tsfn_blocking);\n");
1283 out.push_str("}\n\n");
1284
1285 out.push_str(&format!(
1287 "static void {calljs}(napi_env env, napi_value js_cb, void* context, void* data) {{\n"
1288 ));
1289 out.push_str(" (void)js_cb;\n");
1290 out.push_str(" (void)context;\n");
1291 out.push_str(&format!(" {actx}* ctx = ({actx}*)data;\n"));
1292 out.push_str(" if (env != NULL) {\n");
1293 out.push_str(" if (ctx->err_code != 0) {\n");
1294 out.push_str(" napi_value err_msg;\n");
1295 out.push_str(
1296 " napi_create_string_utf8(env, ctx->err_msg ? ctx->err_msg : \"\", NAPI_AUTO_LENGTH, &err_msg);\n",
1297 );
1298 out.push_str(" napi_value err_obj;\n");
1299 out.push_str(" napi_create_error(env, NULL, err_msg, &err_obj);\n");
1300 out.push_str(" napi_value err_code;\n");
1301 out.push_str(" napi_create_int32(env, ctx->err_code, &err_code);\n");
1302 out.push_str(" napi_set_named_property(env, err_obj, \"code\", err_code);\n");
1303 out.push_str(" napi_reject_deferred(env, ctx->deferred, err_obj);\n");
1304 out.push_str(" } else {\n");
1305 out.push_str(" napi_value val;\n");
1306 match f.ret.as_ref() {
1307 None => out.push_str(" napi_get_undefined(env, &val);\n"),
1308 Some(TypeRef::I32) => out.push_str(" napi_create_int32(env, ctx->result, &val);\n"),
1309 Some(TypeRef::U32) => out.push_str(" napi_create_uint32(env, ctx->result, &val);\n"),
1310 Some(TypeRef::I64) => out.push_str(" napi_create_int64(env, ctx->result, &val);\n"),
1311 Some(TypeRef::F64) => out.push_str(" napi_create_double(env, ctx->result, &val);\n"),
1312 Some(TypeRef::I8 | TypeRef::I16) => {
1313 out.push_str(" napi_create_int32(env, ctx->result, &val);\n")
1314 }
1315 Some(TypeRef::U8 | TypeRef::U16) => {
1316 out.push_str(" napi_create_uint32(env, ctx->result, &val);\n")
1317 }
1318 Some(TypeRef::U64) => {
1319 out.push_str(" napi_create_int64(env, (int64_t)ctx->result, &val);\n")
1320 }
1321 Some(TypeRef::F32) => out.push_str(" napi_create_double(env, ctx->result, &val);\n"),
1322 Some(TypeRef::Bool) => out.push_str(" napi_get_boolean(env, ctx->result, &val);\n"),
1323 Some(TypeRef::Enum(_)) => {
1324 out.push_str(" napi_create_int32(env, ctx->result, &val);\n");
1325 }
1326 Some(TypeRef::StringUtf8 | TypeRef::BorrowedStr) => {
1327 out.push_str(
1328 " if (ctx->result_null) napi_get_null(env, &val); else napi_create_string_utf8(env, ctx->result ? ctx->result : \"\", NAPI_AUTO_LENGTH, &val);\n",
1329 );
1330 }
1331 Some(TypeRef::Bytes | TypeRef::BorrowedBytes) => {
1332 out.push_str(
1333 " napi_create_buffer_copy(env, ctx->result_len, ctx->result ? (const void*)ctx->result : (const void*)\"\", NULL, &val);\n",
1334 );
1335 }
1336 Some(TypeRef::Handle) => {
1337 out.push_str(" napi_create_int64(env, (int64_t)ctx->result, &val);\n");
1338 }
1339 Some(TypeRef::TypedHandle(_) | TypeRef::Iterator(_)) => {
1340 out.push_str(" napi_create_int64(env, (int64_t)(intptr_t)ctx->result, &val);\n");
1341 }
1342 Some(TypeRef::Struct(name)) => {
1343 emit_struct_to_object(
1344 out,
1345 "env",
1346 name,
1347 "ctx->result",
1348 "val",
1349 module,
1350 prefix,
1351 structs,
1352 " ",
1353 true,
1354 );
1355 }
1356 Some(TypeRef::Optional(inner)) => match inner.as_ref() {
1357 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1358 out.push_str(
1359 " if (ctx->result_null) napi_get_null(env, &val); else napi_create_string_utf8(env, ctx->result ? ctx->result : \"\", NAPI_AUTO_LENGTH, &val);\n",
1360 );
1361 }
1362 TypeRef::Struct(name) => {
1363 out.push_str(
1364 " if (ctx->result == NULL) { napi_get_null(env, &val); } else {\n",
1365 );
1366 emit_struct_to_object(
1367 out,
1368 "env",
1369 name,
1370 "ctx->result",
1371 "val",
1372 module,
1373 prefix,
1374 structs,
1375 " ",
1376 true,
1377 );
1378 out.push_str(" }\n");
1379 }
1380 TypeRef::TypedHandle(_) => {
1381 out.push_str(
1382 " if (ctx->result == NULL) napi_get_null(env, &val); else napi_create_int64(env, (int64_t)(intptr_t)ctx->result, &val);\n",
1383 );
1384 }
1385 other => {
1386 let leaf = payload_leaf_to_napi(other, "ctx->result", "val");
1387 out.push_str(&format!(
1388 " if (ctx->result_has) {{ {leaf} }} else napi_get_null(env, &val);\n"
1389 ));
1390 }
1391 },
1392 Some(TypeRef::List(inner)) => {
1393 out.push_str(" napi_create_array(env, &val);\n");
1394 out.push_str(
1395 " for (size_t i = 0; ctx->result != NULL && i < ctx->result_len; i++) {\n",
1396 );
1397 out.push_str(" napi_value elem;\n");
1398 let leaf = payload_elem_to_napi(inner, "ctx->result[i]", "elem");
1399 out.push_str(&format!(" {leaf}\n"));
1400 out.push_str(" napi_set_element(env, val, (uint32_t)i, elem);\n");
1401 out.push_str(" }\n");
1402 }
1403 Some(TypeRef::Map(k, v)) => {
1404 out.push_str(" napi_create_object(env, &val);\n");
1405 out.push_str(
1406 " for (size_t i = 0; ctx->result_keys != NULL && ctx->result_values != NULL && i < ctx->result_len; i++) {\n",
1407 );
1408 out.push_str(" napi_value mk; napi_value mv;\n");
1409 let kc = payload_elem_to_napi(k, "ctx->result_keys[i]", "mk");
1410 let vc = payload_elem_to_napi(v, "ctx->result_values[i]", "mv");
1411 out.push_str(&format!(" {kc}\n"));
1412 out.push_str(&format!(" {vc}\n"));
1413 out.push_str(" napi_set_property(env, val, mk, mv);\n");
1414 out.push_str(" }\n");
1415 }
1416 }
1417 out.push_str(" napi_resolve_deferred(env, ctx->deferred, val);\n");
1418 out.push_str(" }\n");
1419 out.push_str(" }\n");
1420 out.push_str(" free(ctx->err_msg);\n");
1421 match f.ret.as_ref() {
1422 Some(
1423 TypeRef::StringUtf8 | TypeRef::BorrowedStr | TypeRef::Bytes | TypeRef::BorrowedBytes,
1424 ) => {
1425 out.push_str(" free(ctx->result);\n");
1426 }
1427 Some(TypeRef::Optional(inner))
1428 if matches!(inner.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) =>
1429 {
1430 out.push_str(" free(ctx->result);\n");
1431 }
1432 Some(TypeRef::List(inner)) => {
1433 if matches!(inner.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) {
1434 out.push_str(
1435 " for (size_t i = 0; ctx->result != NULL && i < ctx->result_len; i++) free(ctx->result[i]);\n",
1436 );
1437 }
1438 out.push_str(" free(ctx->result);\n");
1439 }
1440 Some(TypeRef::Map(k, v)) => {
1441 for (field, ty) in [("result_keys", k), ("result_values", v)] {
1442 if matches!(ty.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) {
1443 out.push_str(&format!(
1444 " for (size_t i = 0; ctx->{field} != NULL && i < ctx->result_len; i++) free(ctx->{field}[i]);\n"
1445 ));
1446 }
1447 out.push_str(&format!(" free(ctx->{field});\n"));
1448 }
1449 }
1450 _ => {}
1451 }
1452 out.push_str(" napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_release);\n");
1453 out.push_str(" free(ctx);\n");
1454 out.push_str("}\n\n");
1455}
1456
1457fn render_async_napi_body(
1458 out: &mut String,
1459 f: &FnBinding,
1460 c_name: &str,
1461 module: &str,
1462 prefix: &str,
1463) {
1464 let n = f.params.len();
1465 if n > 0 {
1466 out.push_str(&format!(" size_t argc = {n};\n"));
1467 out.push_str(&format!(" napi_value args[{n}];\n"));
1468 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
1469 } else {
1470 out.push_str(" size_t argc = 0;\n");
1471 out.push_str(" napi_get_cb_info(env, info, &argc, NULL, NULL, NULL);\n");
1472 }
1473
1474 let mut c_args: Vec<String> = Vec::new();
1475 let mut cleanups: Vec<String> = Vec::new();
1476 for (i, p) in f.params.iter().enumerate() {
1477 emit_param(
1478 out,
1479 &mut c_args,
1480 &mut cleanups,
1481 &p.ty,
1482 &p.name,
1483 i,
1484 module,
1485 prefix,
1486 );
1487 }
1488
1489 let actx = format!("{c_name}_napi_actx");
1490 out.push_str(&format!(
1491 " {actx}* ctx = ({actx}*)calloc(1, sizeof({actx}));\n"
1492 ));
1493 out.push_str(" napi_value promise;\n");
1494 out.push_str(" napi_create_promise(env, &ctx->deferred, &promise);\n");
1495 out.push_str(" napi_value resource_name;\n");
1496 out.push_str(&format!(
1497 " napi_create_string_utf8(env, \"{c_name}\", NAPI_AUTO_LENGTH, &resource_name);\n"
1498 ));
1499 out.push_str(&format!(
1501 " napi_create_threadsafe_function(env, NULL, NULL, resource_name, 0, 1, NULL, NULL, NULL, {c_name}_napi_settle, &ctx->tsfn);\n"
1502 ));
1503
1504 if f.cancellable {
1505 c_args.push("NULL".into());
1506 }
1507
1508 let cb_name = format!("{c_name}_napi_cb");
1509 c_args.push(cb_name);
1510 c_args.push("ctx".into());
1511 let args_str = c_args.join(", ");
1512 out.push_str(&format!(" {c_name}_async({args_str});\n"));
1513
1514 for cleanup in &cleanups {
1515 out.push_str(cleanup);
1516 }
1517
1518 out.push_str(" return promise;\n");
1519}
1520
1521fn render_napi_body(
1522 out: &mut String,
1523 f: &FnBinding,
1524 c_name: &str,
1525 module: &str,
1526 prefix: &str,
1527 structs: &HashMap<String, StructBinding>,
1528) {
1529 let n = f.params.len();
1530 if n > 0 {
1531 out.push_str(&format!(" size_t argc = {n};\n"));
1532 out.push_str(&format!(" napi_value args[{n}];\n"));
1533 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
1534 } else {
1535 out.push_str(" size_t argc = 0;\n");
1536 out.push_str(" napi_get_cb_info(env, info, &argc, NULL, NULL, NULL);\n");
1537 }
1538
1539 let mut c_args: Vec<String> = Vec::new();
1540 let mut cleanups: Vec<String> = Vec::new();
1541 for (i, p) in f.params.iter().enumerate() {
1542 emit_param(
1543 out,
1544 &mut c_args,
1545 &mut cleanups,
1546 &p.ty,
1547 &p.name,
1548 i,
1549 module,
1550 prefix,
1551 );
1552 }
1553
1554 out.push_str(" weaveffi_error err = {0};\n");
1555
1556 if let Some(ret) = &f.ret {
1557 emit_ret_out_params(out, &mut c_args, ret, module, prefix);
1558 }
1559 c_args.push("&err".to_string());
1560
1561 let args_str = c_args.join(", ");
1562 let ret_type = f.ret.as_ref().map(|r| c_ret_type_str(r, module, prefix));
1563 match &ret_type {
1564 Some(rt) if rt != "void" => {
1565 out.push_str(&format!(" {rt} result = {c_name}({args_str});\n"));
1566 }
1567 _ => {
1568 out.push_str(&format!(" {c_name}({args_str});\n"));
1569 }
1570 }
1571
1572 for cleanup in &cleanups {
1573 out.push_str(cleanup);
1574 }
1575
1576 out.push_str(" if (err.code != 0) {\n");
1577 out.push_str(" napi_throw_error(env, NULL, err.message);\n");
1578 out.push_str(" weaveffi_error_clear(&err);\n");
1579 out.push_str(" return NULL;\n");
1580 out.push_str(" }\n");
1581
1582 match &f.ret {
1583 Some(ret) => emit_ret_to_napi(out, ret, module, prefix, &f.name, structs),
1584 None => {
1585 out.push_str(" napi_value ret;\n");
1586 out.push_str(" napi_get_undefined(env, &ret);\n");
1587 out.push_str(" return ret;\n");
1588 }
1589 }
1590}
1591
1592#[allow(clippy::too_many_arguments)]
1593fn emit_param(
1594 out: &mut String,
1595 c_args: &mut Vec<String>,
1596 cleanups: &mut Vec<String>,
1597 ty: &TypeRef,
1598 name: &str,
1599 idx: usize,
1600 module: &str,
1601 prefix: &str,
1602) {
1603 match ty {
1604 TypeRef::I32 | TypeRef::U32 | TypeRef::I64 | TypeRef::F64 | TypeRef::Bool => {
1605 let ct = c_elem_type(ty, module, prefix);
1606 let getter = napi_getter(ty);
1607 out.push_str(&format!(" {ct} {name};\n"));
1608 out.push_str(&format!(" {getter}(env, args[{idx}], &{name});\n"));
1609 c_args.push(name.into());
1610 }
1611 TypeRef::I8 | TypeRef::I16 | TypeRef::U8 | TypeRef::U16 | TypeRef::U64 | TypeRef::F32 => {
1614 let ct = c_elem_type(ty, module, prefix);
1615 let getter = napi_getter(ty);
1616 let raw = napi_read_tmp_type(ty);
1617 out.push_str(&format!(" {raw} {name}_raw;\n"));
1618 out.push_str(&format!(" {getter}(env, args[{idx}], &{name}_raw);\n"));
1619 c_args.push(format!("({ct}){name}_raw"));
1620 }
1621 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1622 out.push_str(&format!(" size_t {name}_len;\n"));
1623 out.push_str(&format!(
1624 " napi_get_value_string_utf8(env, args[{idx}], NULL, 0, &{name}_len);\n"
1625 ));
1626 out.push_str(&format!(
1627 " char* {name} = (char*)malloc({name}_len + 1);\n"
1628 ));
1629 out.push_str(&format!(
1630 " napi_get_value_string_utf8(env, args[{idx}], {name}, {name}_len + 1, &{name}_len);\n"
1631 ));
1632 c_args.push(name.into());
1633 cleanups.push(format!(" free({name});\n"));
1634 }
1635 TypeRef::Handle => {
1636 out.push_str(&format!(" int64_t {name}_raw;\n"));
1637 out.push_str(&format!(
1638 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1639 ));
1640 c_args.push(format!("(weaveffi_handle_t){name}_raw"));
1641 }
1642 TypeRef::TypedHandle(s) => {
1643 let abi = c_abi_struct_name(s, module, prefix);
1644 out.push_str(&format!(" int64_t {name}_raw;\n"));
1645 out.push_str(&format!(
1646 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1647 ));
1648 c_args.push(format!("({abi}*)(intptr_t){name}_raw"));
1649 }
1650 TypeRef::Enum(e) => {
1651 out.push_str(&format!(" int32_t {name};\n"));
1652 out.push_str(&format!(
1653 " napi_get_value_int32(env, args[{idx}], &{name});\n"
1654 ));
1655 c_args.push(format!("({prefix}_{module}_{e}){name}"));
1656 }
1657 TypeRef::Struct(s) => {
1658 let abi = c_abi_struct_name(s, module, prefix);
1659 out.push_str(&format!(" int64_t {name}_raw;\n"));
1660 out.push_str(&format!(
1661 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1662 ));
1663 c_args.push(format!("(const {abi}*)(intptr_t){name}_raw"));
1664 }
1665 TypeRef::Optional(inner) => {
1666 out.push_str(&format!(" napi_valuetype {name}_type;\n"));
1667 out.push_str(&format!(" napi_typeof(env, args[{idx}], &{name}_type);\n"));
1668 emit_optional_param(out, c_args, cleanups, inner, name, idx, module, prefix);
1669 }
1670 TypeRef::List(inner) => {
1671 emit_list_param(out, c_args, cleanups, inner, name, idx, module, prefix);
1672 }
1673 TypeRef::Bytes | TypeRef::BorrowedBytes => {
1674 out.push_str(&format!(" void* {name}_raw;\n"));
1675 out.push_str(&format!(" size_t {name}_len;\n"));
1676 out.push_str(&format!(
1677 " napi_get_buffer_info(env, args[{idx}], &{name}_raw, &{name}_len);\n"
1678 ));
1679 c_args.push(format!("(const uint8_t*){name}_raw"));
1680 c_args.push(format!("{name}_len"));
1681 }
1682 TypeRef::Map(k, v) => {
1683 emit_map_param(out, c_args, cleanups, k, v, name, idx, module, prefix);
1684 }
1685 TypeRef::Iterator(_) => unreachable!("iterator not valid as parameter"),
1686 }
1687}
1688
1689fn emit_opt_val(
1690 out: &mut String,
1691 c_args: &mut Vec<String>,
1692 c_type: &str,
1693 napi_fn: &str,
1694 name: &str,
1695 idx: usize,
1696) {
1697 out.push_str(&format!(" {c_type} {name}_val;\n"));
1698 out.push_str(&format!(" const {c_type}* {name}_ptr = NULL;\n"));
1699 out.push_str(&format!(
1700 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1701 ));
1702 out.push_str(&format!(" {napi_fn}(env, args[{idx}], &{name}_val);\n"));
1703 out.push_str(&format!(" {name}_ptr = &{name}_val;\n"));
1704 out.push_str(" }\n");
1705 c_args.push(format!("{name}_ptr"));
1706}
1707
1708#[allow(clippy::too_many_arguments)]
1709fn emit_optional_param(
1710 out: &mut String,
1711 c_args: &mut Vec<String>,
1712 cleanups: &mut Vec<String>,
1713 inner: &TypeRef,
1714 name: &str,
1715 idx: usize,
1716 module: &str,
1717 prefix: &str,
1718) {
1719 match inner {
1720 TypeRef::I32 => {
1721 emit_opt_val(out, c_args, "int32_t", "napi_get_value_int32", name, idx);
1722 }
1723 TypeRef::U32 => {
1724 emit_opt_val(out, c_args, "uint32_t", "napi_get_value_uint32", name, idx);
1725 }
1726 TypeRef::I64 => {
1727 emit_opt_val(out, c_args, "int64_t", "napi_get_value_int64", name, idx);
1728 }
1729 TypeRef::F64 => {
1730 emit_opt_val(out, c_args, "double", "napi_get_value_double", name, idx);
1731 }
1732 TypeRef::Bool => {
1733 emit_opt_val(out, c_args, "bool", "napi_get_value_bool", name, idx);
1734 }
1735 TypeRef::Handle => {
1736 out.push_str(&format!(" int64_t {name}_raw = 0;\n"));
1737 out.push_str(&format!(" weaveffi_handle_t {name}_val;\n"));
1738 out.push_str(&format!(" const weaveffi_handle_t* {name}_ptr = NULL;\n"));
1739 out.push_str(&format!(
1740 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1741 ));
1742 out.push_str(&format!(
1743 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1744 ));
1745 out.push_str(&format!(
1746 " {name}_val = (weaveffi_handle_t){name}_raw;\n"
1747 ));
1748 out.push_str(&format!(" {name}_ptr = &{name}_val;\n"));
1749 out.push_str(" }\n");
1750 c_args.push(format!("{name}_ptr"));
1751 }
1752 TypeRef::TypedHandle(s) => {
1755 let abi = c_abi_struct_name(s, module, prefix);
1756 out.push_str(&format!(" int64_t {name}_raw = 0;\n"));
1757 out.push_str(&format!(
1758 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1759 ));
1760 out.push_str(&format!(
1761 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1762 ));
1763 out.push_str(" }\n");
1764 c_args.push(format!("{name}_raw ? ({abi}*)(intptr_t){name}_raw : NULL"));
1765 }
1766 TypeRef::Enum(e) => {
1767 let etype = format!("{prefix}_{module}_{e}");
1768 out.push_str(&format!(" int32_t {name}_raw;\n"));
1769 out.push_str(&format!(" {etype} {name}_val;\n"));
1770 out.push_str(&format!(" const {etype}* {name}_ptr = NULL;\n"));
1771 out.push_str(&format!(
1772 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1773 ));
1774 out.push_str(&format!(
1775 " napi_get_value_int32(env, args[{idx}], &{name}_raw);\n"
1776 ));
1777 out.push_str(&format!(" {name}_val = ({etype}){name}_raw;\n"));
1778 out.push_str(&format!(" {name}_ptr = &{name}_val;\n"));
1779 out.push_str(" }\n");
1780 c_args.push(format!("{name}_ptr"));
1781 }
1782 TypeRef::StringUtf8 => {
1783 out.push_str(&format!(" char* {name} = NULL;\n"));
1784 out.push_str(&format!(
1785 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1786 ));
1787 out.push_str(&format!(" size_t {name}_len;\n"));
1788 out.push_str(&format!(
1789 " napi_get_value_string_utf8(env, args[{idx}], NULL, 0, &{name}_len);\n"
1790 ));
1791 out.push_str(&format!(" {name} = (char*)malloc({name}_len + 1);\n"));
1792 out.push_str(&format!(
1793 " napi_get_value_string_utf8(env, args[{idx}], {name}, {name}_len + 1, &{name}_len);\n"
1794 ));
1795 out.push_str(" }\n");
1796 c_args.push(name.into());
1797 cleanups.push(format!(" free({name});\n"));
1798 }
1799 TypeRef::Struct(s) => {
1800 let abi = c_abi_struct_name(s, module, prefix);
1801 out.push_str(&format!(" int64_t {name}_raw = 0;\n"));
1802 out.push_str(&format!(
1803 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1804 ));
1805 out.push_str(&format!(
1806 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1807 ));
1808 out.push_str(" }\n");
1809 c_args.push(format!(
1810 "{name}_raw ? (const {abi}*)(intptr_t){name}_raw : NULL"
1811 ));
1812 }
1813 TypeRef::I8 | TypeRef::I16 | TypeRef::U8 | TypeRef::U16 | TypeRef::U64 | TypeRef::F32 => {
1816 let ct = c_elem_type(inner, module, prefix);
1817 let getter = napi_getter(inner);
1818 let raw = napi_read_tmp_type(inner);
1819 out.push_str(&format!(" {ct} {name}_val;\n"));
1820 out.push_str(&format!(" const {ct}* {name}_ptr = NULL;\n"));
1821 out.push_str(&format!(
1822 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1823 ));
1824 out.push_str(&format!(" {raw} {name}_raw;\n"));
1825 out.push_str(&format!(" {getter}(env, args[{idx}], &{name}_raw);\n"));
1826 out.push_str(&format!(" {name}_val = ({ct}){name}_raw;\n"));
1827 out.push_str(&format!(" {name}_ptr = &{name}_val;\n"));
1828 out.push_str(" }\n");
1829 c_args.push(format!("{name}_ptr"));
1830 }
1831 _ => {
1832 emit_param(out, c_args, cleanups, inner, name, idx, module, prefix);
1833 }
1834 }
1835}
1836
1837#[allow(clippy::too_many_arguments)]
1838fn emit_list_param(
1839 out: &mut String,
1840 c_args: &mut Vec<String>,
1841 cleanups: &mut Vec<String>,
1842 inner: &TypeRef,
1843 name: &str,
1844 idx: usize,
1845 module: &str,
1846 prefix: &str,
1847) {
1848 let et = c_elem_type(inner, module, prefix);
1849 out.push_str(&format!(" uint32_t {name}_count;\n"));
1850 out.push_str(&format!(
1851 " napi_get_array_length(env, args[{idx}], &{name}_count);\n"
1852 ));
1853 out.push_str(&format!(
1854 " {et}* {name}_arr = ({et}*)malloc(sizeof({et}) * ({name}_count + 1));\n"
1855 ));
1856 out.push_str(&format!(
1857 " for (uint32_t {name}_i = 0; {name}_i < {name}_count; {name}_i++) {{\n"
1858 ));
1859 out.push_str(&format!(" napi_value {name}_el;\n"));
1860 out.push_str(&format!(
1861 " napi_get_element(env, args[{idx}], {name}_i, &{name}_el);\n"
1862 ));
1863
1864 match inner {
1865 TypeRef::I32 | TypeRef::U32 | TypeRef::I64 | TypeRef::F64 | TypeRef::Bool => {
1866 let getter = napi_getter(inner);
1867 out.push_str(&format!(
1868 " {getter}(env, {name}_el, &{name}_arr[{name}_i]);\n"
1869 ));
1870 }
1871 TypeRef::I8 | TypeRef::I16 | TypeRef::U8 | TypeRef::U16 | TypeRef::U64 | TypeRef::F32 => {
1874 let getter = napi_getter(inner);
1875 let raw = napi_read_tmp_type(inner);
1876 out.push_str(&format!(" {raw} {name}_nv;\n"));
1877 out.push_str(&format!(" {getter}(env, {name}_el, &{name}_nv);\n"));
1878 out.push_str(&format!(" {name}_arr[{name}_i] = ({et}){name}_nv;\n"));
1879 }
1880 TypeRef::Handle => {
1881 out.push_str(&format!(" int64_t {name}_h;\n"));
1882 out.push_str(&format!(
1883 " napi_get_value_int64(env, {name}_el, &{name}_h);\n"
1884 ));
1885 out.push_str(&format!(
1886 " {name}_arr[{name}_i] = (weaveffi_handle_t){name}_h;\n"
1887 ));
1888 }
1889 TypeRef::TypedHandle(s) => {
1890 let abi = c_abi_struct_name(s, module, prefix);
1891 out.push_str(&format!(" int64_t {name}_h;\n"));
1892 out.push_str(&format!(
1893 " napi_get_value_int64(env, {name}_el, &{name}_h);\n"
1894 ));
1895 out.push_str(&format!(
1896 " {name}_arr[{name}_i] = ({abi}*)(intptr_t){name}_h;\n"
1897 ));
1898 }
1899 TypeRef::Enum(_) => {
1900 out.push_str(&format!(" int32_t {name}_ev;\n"));
1901 out.push_str(&format!(
1902 " napi_get_value_int32(env, {name}_el, &{name}_ev);\n"
1903 ));
1904 out.push_str(&format!(" {name}_arr[{name}_i] = ({et}){name}_ev;\n"));
1905 }
1906 TypeRef::StringUtf8 => {
1907 out.push_str(&format!(" size_t {name}_sl;\n"));
1908 out.push_str(&format!(
1909 " napi_get_value_string_utf8(env, {name}_el, NULL, 0, &{name}_sl);\n"
1910 ));
1911 out.push_str(&format!(
1912 " char* {name}_s = (char*)malloc({name}_sl + 1);\n"
1913 ));
1914 out.push_str(&format!(
1915 " napi_get_value_string_utf8(env, {name}_el, {name}_s, {name}_sl + 1, &{name}_sl);\n"
1916 ));
1917 out.push_str(&format!(" {name}_arr[{name}_i] = {name}_s;\n"));
1918 }
1919 TypeRef::Struct(_) => {
1920 out.push_str(&format!(" int64_t {name}_sp;\n"));
1921 out.push_str(&format!(
1922 " napi_get_value_int64(env, {name}_el, &{name}_sp);\n"
1923 ));
1924 out.push_str(&format!(
1925 " {name}_arr[{name}_i] = ({et})(intptr_t){name}_sp;\n"
1926 ));
1927 }
1928 _ => {
1929 let getter = napi_getter(inner);
1930 out.push_str(&format!(
1931 " {getter}(env, {name}_el, &{name}_arr[{name}_i]);\n"
1932 ));
1933 }
1934 }
1935
1936 out.push_str(" }\n");
1937 c_args.push(format!("{name}_arr"));
1938 c_args.push(format!("(size_t){name}_count"));
1939
1940 if matches!(inner, TypeRef::StringUtf8) {
1941 cleanups.push(format!(
1942 " for (uint32_t {name}_j = 0; {name}_j < {name}_count; {name}_j++) free((void*){name}_arr[{name}_j]);\n"
1943 ));
1944 }
1945 cleanups.push(format!(" free({name}_arr);\n"));
1946}
1947
1948#[allow(clippy::too_many_arguments)]
1949fn emit_map_param(
1950 out: &mut String,
1951 c_args: &mut Vec<String>,
1952 cleanups: &mut Vec<String>,
1953 k: &TypeRef,
1954 v: &TypeRef,
1955 name: &str,
1956 idx: usize,
1957 module: &str,
1958 prefix: &str,
1959) {
1960 let kt = c_elem_type(k, module, prefix);
1961 let vt = c_elem_type(v, module, prefix);
1962 out.push_str(&format!(" napi_value {name}_keys_napi;\n"));
1963 out.push_str(&format!(
1964 " napi_get_property_names(env, args[{idx}], &{name}_keys_napi);\n"
1965 ));
1966 out.push_str(&format!(" uint32_t {name}_count;\n"));
1967 out.push_str(&format!(
1968 " napi_get_array_length(env, {name}_keys_napi, &{name}_count);\n"
1969 ));
1970 out.push_str(&format!(
1971 " {kt}* {name}_keys = ({kt}*)malloc(sizeof({kt}) * ({name}_count + 1));\n"
1972 ));
1973 out.push_str(&format!(
1974 " {vt}* {name}_values = ({vt}*)malloc(sizeof({vt}) * ({name}_count + 1));\n"
1975 ));
1976 out.push_str(&format!(
1977 " for (uint32_t {name}_i = 0; {name}_i < {name}_count; {name}_i++) {{\n"
1978 ));
1979 out.push_str(&format!(" napi_value {name}_k;\n"));
1980 out.push_str(&format!(
1981 " napi_get_element(env, {name}_keys_napi, {name}_i, &{name}_k);\n"
1982 ));
1983
1984 if matches!(k, TypeRef::StringUtf8) {
1985 out.push_str(&format!(" size_t {name}_kl;\n"));
1986 out.push_str(&format!(
1987 " napi_get_value_string_utf8(env, {name}_k, NULL, 0, &{name}_kl);\n"
1988 ));
1989 out.push_str(&format!(
1990 " char* {name}_ks = (char*)malloc({name}_kl + 1);\n"
1991 ));
1992 out.push_str(&format!(
1993 " napi_get_value_string_utf8(env, {name}_k, {name}_ks, {name}_kl + 1, &{name}_kl);\n"
1994 ));
1995 out.push_str(&format!(" {name}_keys[{name}_i] = {name}_ks;\n"));
1996 } else if needs_narrowing_read(k) {
1997 out.push_str(&format!(" napi_value {name}_kn;\n"));
1998 out.push_str(&format!(
1999 " napi_coerce_to_number(env, {name}_k, &{name}_kn);\n"
2000 ));
2001 let kgetter = napi_getter(k);
2002 let raw = napi_read_tmp_type(k);
2003 out.push_str(&format!(" {raw} {name}_kv;\n"));
2004 out.push_str(&format!(" {kgetter}(env, {name}_kn, &{name}_kv);\n"));
2005 out.push_str(&format!(" {name}_keys[{name}_i] = ({kt}){name}_kv;\n"));
2006 } else {
2007 out.push_str(&format!(" napi_value {name}_kn;\n"));
2008 out.push_str(&format!(
2009 " napi_coerce_to_number(env, {name}_k, &{name}_kn);\n"
2010 ));
2011 let kgetter = napi_getter(k);
2012 out.push_str(&format!(
2013 " {kgetter}(env, {name}_kn, &{name}_keys[{name}_i]);\n"
2014 ));
2015 }
2016
2017 out.push_str(&format!(" napi_value {name}_v;\n"));
2018 out.push_str(&format!(
2019 " napi_get_property(env, args[{idx}], {name}_k, &{name}_v);\n"
2020 ));
2021
2022 if matches!(v, TypeRef::StringUtf8) {
2023 out.push_str(&format!(" size_t {name}_vl;\n"));
2024 out.push_str(&format!(
2025 " napi_get_value_string_utf8(env, {name}_v, NULL, 0, &{name}_vl);\n"
2026 ));
2027 out.push_str(&format!(
2028 " char* {name}_vs = (char*)malloc({name}_vl + 1);\n"
2029 ));
2030 out.push_str(&format!(
2031 " napi_get_value_string_utf8(env, {name}_v, {name}_vs, {name}_vl + 1, &{name}_vl);\n"
2032 ));
2033 out.push_str(&format!(" {name}_values[{name}_i] = {name}_vs;\n"));
2034 } else if needs_narrowing_read(v) {
2035 let vgetter = napi_getter(v);
2036 let raw = napi_read_tmp_type(v);
2037 out.push_str(&format!(" {raw} {name}_vv;\n"));
2038 out.push_str(&format!(" {vgetter}(env, {name}_v, &{name}_vv);\n"));
2039 out.push_str(&format!(" {name}_values[{name}_i] = ({vt}){name}_vv;\n"));
2040 } else {
2041 let vgetter = napi_getter(v);
2042 out.push_str(&format!(
2043 " {vgetter}(env, {name}_v, &{name}_values[{name}_i]);\n"
2044 ));
2045 }
2046
2047 out.push_str(" }\n");
2048 c_args.push(format!("{name}_keys"));
2049 c_args.push(format!("{name}_values"));
2050 c_args.push(format!("(size_t){name}_count"));
2051
2052 if matches!(k, TypeRef::StringUtf8) {
2053 cleanups.push(format!(
2054 " for (uint32_t {name}_j = 0; {name}_j < {name}_count; {name}_j++) free((void*){name}_keys[{name}_j]);\n"
2055 ));
2056 }
2057 cleanups.push(format!(" free({name}_keys);\n"));
2058 if matches!(v, TypeRef::StringUtf8) {
2059 cleanups.push(format!(
2060 " for (uint32_t {name}_j = 0; {name}_j < {name}_count; {name}_j++) free((void*){name}_values[{name}_j]);\n"
2061 ));
2062 }
2063 cleanups.push(format!(" free({name}_values);\n"));
2064}
2065
2066fn emit_ret_out_params(
2067 out: &mut String,
2068 c_args: &mut Vec<String>,
2069 ty: &TypeRef,
2070 module: &str,
2071 prefix: &str,
2072) {
2073 match ty {
2074 TypeRef::Bytes | TypeRef::List(_) => {
2075 out.push_str(" size_t out_len;\n");
2076 c_args.push("&out_len".into());
2077 }
2078 TypeRef::Map(k, v) => {
2079 let kt = c_elem_type(k, module, prefix);
2080 let vt = c_elem_type(v, module, prefix);
2081 out.push_str(&format!(" {kt}* out_keys = NULL;\n"));
2082 out.push_str(&format!(" {vt}* out_values = NULL;\n"));
2083 out.push_str(" size_t out_len = 0;\n");
2084 c_args.push("out_keys".into());
2085 c_args.push("out_values".into());
2086 c_args.push("&out_len".into());
2087 }
2088 TypeRef::Optional(inner) if is_c_ptr_type(inner) => {
2089 emit_ret_out_params(out, c_args, inner, module, prefix);
2090 }
2091 _ => {}
2092 }
2093}
2094
2095fn struct_registry(model: &BindingModel) -> HashMap<String, StructBinding> {
2099 model
2100 .modules
2101 .iter()
2102 .flat_map(|m| m.structs.iter())
2103 .map(|s| (s.name.clone(), s.clone()))
2104 .collect()
2105}
2106
2107#[allow(clippy::too_many_arguments)]
2112fn emit_struct_to_object(
2113 out: &mut String,
2114 env: &str,
2115 struct_name: &str,
2116 ptr_expr: &str,
2117 obj_var: &str,
2118 module: &str,
2119 prefix: &str,
2120 structs: &HashMap<String, StructBinding>,
2121 indent: &str,
2122 destroy: bool,
2123) {
2124 let Some(def) = structs.get(local_type_name(struct_name)).cloned() else {
2125 out.push_str(&format!(
2127 "{indent}napi_create_int64({env}, (int64_t)(intptr_t){ptr_expr}, &{obj_var});\n"
2128 ));
2129 return;
2130 };
2131 let abi = &def.c_tag;
2132 let p = format!("{obj_var}_p");
2133 out.push_str(&format!("{indent}{{\n"));
2134 out.push_str(&format!("{indent} {abi}* {p} = ({abi}*){ptr_expr};\n"));
2135 out.push_str(&format!(
2136 "{indent} napi_create_object({env}, &{obj_var});\n"
2137 ));
2138 for field in &def.fields {
2139 let getter = &field.getter_symbol;
2140 let fv = format!("{obj_var}_{}", field.name);
2141 out.push_str(&format!("{indent} napi_value {fv};\n"));
2142 emit_struct_field_to_napi(
2143 out,
2144 env,
2145 &field.ty,
2146 getter,
2147 &p,
2148 &fv,
2149 module,
2150 prefix,
2151 structs,
2152 &format!("{indent} "),
2153 );
2154 out.push_str(&format!(
2155 "{indent} napi_set_named_property({env}, {obj_var}, \"{}\", {fv});\n",
2156 field.name
2157 ));
2158 }
2159 if destroy {
2160 out.push_str(&format!("{indent} {}({p});\n", def.destroy_symbol));
2161 }
2162 out.push_str(&format!("{indent}}}\n"));
2163}
2164
2165fn napi_create_leaf(env: &str, ty: &TypeRef, expr: &str, target: &str) -> String {
2169 match ty {
2170 TypeRef::I32 => format!("napi_create_int32({env}, {expr}, &{target});"),
2171 TypeRef::U32 => format!("napi_create_uint32({env}, {expr}, &{target});"),
2172 TypeRef::I64 => format!("napi_create_int64({env}, {expr}, &{target});"),
2173 TypeRef::F64 => format!("napi_create_double({env}, {expr}, &{target});"),
2174 TypeRef::I8 | TypeRef::I16 => format!("napi_create_int32({env}, {expr}, &{target});"),
2175 TypeRef::U8 | TypeRef::U16 => format!("napi_create_uint32({env}, {expr}, &{target});"),
2176 TypeRef::U64 => format!("napi_create_int64({env}, (int64_t)({expr}), &{target});"),
2177 TypeRef::F32 => format!("napi_create_double({env}, {expr}, &{target});"),
2178 TypeRef::Bool => format!("napi_get_boolean({env}, {expr}, &{target});"),
2179 TypeRef::Enum(_) => format!("napi_create_int32({env}, (int32_t)({expr}), &{target});"),
2180 TypeRef::Handle | TypeRef::TypedHandle(_) => {
2181 format!("napi_create_int64({env}, (int64_t)(intptr_t)({expr}), &{target});")
2182 }
2183 _ => format!("napi_get_null({env}, &{target});"),
2184 }
2185}
2186
2187#[allow(clippy::too_many_arguments)]
2191fn emit_elem_to_napi(
2192 out: &mut String,
2193 env: &str,
2194 ty: &TypeRef,
2195 expr: &str,
2196 target: &str,
2197 module: &str,
2198 prefix: &str,
2199 structs: &HashMap<String, StructBinding>,
2200 indent: &str,
2201) {
2202 match ty {
2203 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
2204 out.push_str(&format!(
2205 "{indent}napi_create_string_utf8({env}, {expr}, NAPI_AUTO_LENGTH, &{target});\n"
2206 ));
2207 if matches!(ty, TypeRef::StringUtf8) {
2208 out.push_str(&format!("{indent}weaveffi_free_string((char*)({expr}));\n"));
2209 }
2210 }
2211 TypeRef::Struct(name) => {
2212 emit_struct_to_object(
2213 out, env, name, expr, target, module, prefix, structs, indent, false,
2214 );
2215 }
2216 _ => out.push_str(&format!(
2217 "{indent}{}\n",
2218 napi_create_leaf(env, ty, expr, target)
2219 )),
2220 }
2221}
2222
2223#[allow(clippy::too_many_arguments)]
2227fn emit_struct_field_to_napi(
2228 out: &mut String,
2229 env: &str,
2230 ty: &TypeRef,
2231 getter: &str,
2232 pv: &str,
2233 fv: &str,
2234 module: &str,
2235 prefix: &str,
2236 structs: &HashMap<String, StructBinding>,
2237 indent: &str,
2238) {
2239 match ty {
2240 TypeRef::I32 => out.push_str(&format!(
2241 "{indent}napi_create_int32({env}, {getter}({pv}), &{fv});\n"
2242 )),
2243 TypeRef::U32 => out.push_str(&format!(
2244 "{indent}napi_create_uint32({env}, {getter}({pv}), &{fv});\n"
2245 )),
2246 TypeRef::I64 => out.push_str(&format!(
2247 "{indent}napi_create_int64({env}, {getter}({pv}), &{fv});\n"
2248 )),
2249 TypeRef::F64 => out.push_str(&format!(
2250 "{indent}napi_create_double({env}, {getter}({pv}), &{fv});\n"
2251 )),
2252 TypeRef::I8 | TypeRef::I16 => out.push_str(&format!(
2253 "{indent}napi_create_int32({env}, {getter}({pv}), &{fv});\n"
2254 )),
2255 TypeRef::U8 | TypeRef::U16 => out.push_str(&format!(
2256 "{indent}napi_create_uint32({env}, {getter}({pv}), &{fv});\n"
2257 )),
2258 TypeRef::U64 => out.push_str(&format!(
2259 "{indent}napi_create_int64({env}, (int64_t){getter}({pv}), &{fv});\n"
2260 )),
2261 TypeRef::F32 => out.push_str(&format!(
2262 "{indent}napi_create_double({env}, {getter}({pv}), &{fv});\n"
2263 )),
2264 TypeRef::Bool => out.push_str(&format!(
2265 "{indent}napi_get_boolean({env}, {getter}({pv}), &{fv});\n"
2266 )),
2267 TypeRef::Enum(_) => out.push_str(&format!(
2268 "{indent}napi_create_int32({env}, (int32_t){getter}({pv}), &{fv});\n"
2269 )),
2270 TypeRef::Handle | TypeRef::TypedHandle(_) => out.push_str(&format!(
2271 "{indent}napi_create_int64({env}, (int64_t)(intptr_t){getter}({pv}), &{fv});\n"
2272 )),
2273 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
2274 let owned = matches!(ty, TypeRef::StringUtf8);
2275 out.push_str(&format!("{indent}{{\n"));
2276 out.push_str(&format!(
2277 "{indent} char* {fv}_s = (char*){getter}({pv});\n"
2278 ));
2279 out.push_str(&format!(
2280 "{indent} napi_create_string_utf8({env}, {fv}_s, NAPI_AUTO_LENGTH, &{fv});\n"
2281 ));
2282 if owned {
2283 out.push_str(&format!("{indent} weaveffi_free_string({fv}_s);\n"));
2284 }
2285 out.push_str(&format!("{indent}}}\n"));
2286 }
2287 TypeRef::Struct(name) => {
2288 emit_struct_to_object(
2289 out,
2290 env,
2291 name,
2292 &format!("{getter}({pv})"),
2293 fv,
2294 module,
2295 prefix,
2296 structs,
2297 indent,
2298 true,
2299 );
2300 }
2301 TypeRef::Optional(inner)
2302 if matches!(inner.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) =>
2303 {
2304 let owned = matches!(inner.as_ref(), TypeRef::StringUtf8);
2305 out.push_str(&format!("{indent}{{\n"));
2306 out.push_str(&format!(
2307 "{indent} char* {fv}_s = (char*){getter}({pv});\n"
2308 ));
2309 out.push_str(&format!(
2310 "{indent} if ({fv}_s == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
2311 ));
2312 out.push_str(&format!(
2313 "{indent} else {{ napi_create_string_utf8({env}, {fv}_s, NAPI_AUTO_LENGTH, &{fv});"
2314 ));
2315 if owned {
2316 out.push_str(&format!(" weaveffi_free_string({fv}_s);"));
2317 }
2318 out.push_str(" }\n");
2319 out.push_str(&format!("{indent}}}\n"));
2320 }
2321 TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Struct(_)) => {
2322 let TypeRef::Struct(name) = inner.as_ref() else {
2323 unreachable!()
2324 };
2325 let abi = c_abi_struct_name(name, module, prefix);
2326 out.push_str(&format!("{indent}{{\n"));
2327 out.push_str(&format!("{indent} {abi}* {fv}_sp = {getter}({pv});\n"));
2328 out.push_str(&format!(
2329 "{indent} if ({fv}_sp == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
2330 ));
2331 out.push_str(&format!("{indent} else {{\n"));
2332 emit_struct_to_object(
2333 out,
2334 env,
2335 name,
2336 &format!("{fv}_sp"),
2337 fv,
2338 module,
2339 prefix,
2340 structs,
2341 &format!("{indent} "),
2342 true,
2343 );
2344 out.push_str(&format!("{indent} }}\n"));
2345 out.push_str(&format!("{indent}}}\n"));
2346 }
2347 TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::TypedHandle(_)) => {
2351 let TypeRef::TypedHandle(name) = inner.as_ref() else {
2352 unreachable!()
2353 };
2354 let abi = c_abi_struct_name(name, module, prefix);
2355 out.push_str(&format!("{indent}{{\n"));
2356 out.push_str(&format!("{indent} {abi}* {fv}_h = {getter}({pv});\n"));
2357 out.push_str(&format!(
2358 "{indent} if ({fv}_h == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
2359 ));
2360 out.push_str(&format!(
2361 "{indent} else {{ napi_create_int64({env}, (int64_t)(intptr_t){fv}_h, &{fv}); }}\n"
2362 ));
2363 out.push_str(&format!("{indent}}}\n"));
2364 }
2365 TypeRef::Optional(inner) => {
2368 let ct = c_elem_type(inner, module, prefix);
2369 out.push_str(&format!("{indent}{{\n"));
2370 out.push_str(&format!("{indent} {ct}* {fv}_p = {getter}({pv});\n"));
2371 out.push_str(&format!(
2372 "{indent} if ({fv}_p == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
2373 ));
2374 out.push_str(&format!(
2375 "{indent} else {{ {} }}\n",
2376 napi_create_leaf(env, inner, &format!("*{fv}_p"), fv)
2377 ));
2378 out.push_str(&format!("{indent}}}\n"));
2379 }
2380 TypeRef::Bytes | TypeRef::BorrowedBytes => {
2381 out.push_str(&format!("{indent}{{\n"));
2382 out.push_str(&format!("{indent} size_t {fv}_len;\n"));
2383 out.push_str(&format!(
2384 "{indent} const uint8_t* {fv}_data = (const uint8_t*){getter}({pv}, &{fv}_len);\n"
2385 ));
2386 out.push_str(&format!(
2387 "{indent} if ({fv}_data == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
2388 ));
2389 out.push_str(&format!(
2390 "{indent} else {{ void* {fv}_buf; napi_create_buffer_copy({env}, {fv}_len, {fv}_data, &{fv}_buf, &{fv}); }}\n"
2391 ));
2392 out.push_str(&format!("{indent}}}\n"));
2393 }
2394 TypeRef::List(inner) => {
2395 let et = c_elem_type(inner, module, prefix);
2396 out.push_str(&format!("{indent}{{\n"));
2397 out.push_str(&format!("{indent} size_t {fv}_len;\n"));
2398 out.push_str(&format!(
2399 "{indent} {et}* {fv}_arr = {getter}({pv}, &{fv}_len);\n"
2400 ));
2401 out.push_str(&format!("{indent} napi_create_array({env}, &{fv});\n"));
2402 out.push_str(&format!("{indent} if ({fv}_arr != NULL) {{\n"));
2403 out.push_str(&format!(
2404 "{indent} for (size_t {fv}_i = 0; {fv}_i < {fv}_len; {fv}_i++) {{\n"
2405 ));
2406 out.push_str(&format!("{indent} napi_value {fv}_e;\n"));
2407 emit_elem_to_napi(
2408 out,
2409 env,
2410 inner,
2411 &format!("{fv}_arr[{fv}_i]"),
2412 &format!("{fv}_e"),
2413 module,
2414 prefix,
2415 structs,
2416 &format!("{indent} "),
2417 );
2418 out.push_str(&format!(
2419 "{indent} napi_set_element({env}, {fv}, (uint32_t){fv}_i, {fv}_e);\n"
2420 ));
2421 out.push_str(&format!("{indent} }}\n"));
2422 out.push_str(&format!("{indent} }}\n"));
2423 out.push_str(&format!("{indent}}}\n"));
2424 }
2425 TypeRef::Map(k, v) => {
2426 let kt = c_elem_type(k, module, prefix);
2427 let vt = c_elem_type(v, module, prefix);
2428 out.push_str(&format!("{indent}{{\n"));
2429 out.push_str(&format!("{indent} {kt}* {fv}_keys = NULL;\n"));
2430 out.push_str(&format!("{indent} {vt}* {fv}_vals = NULL;\n"));
2431 out.push_str(&format!("{indent} size_t {fv}_len;\n"));
2432 out.push_str(&format!(
2433 "{indent} {getter}({pv}, &{fv}_keys, &{fv}_vals, &{fv}_len);\n"
2434 ));
2435 out.push_str(&format!("{indent} napi_create_object({env}, &{fv});\n"));
2436 out.push_str(&format!(
2437 "{indent} if ({fv}_keys != NULL && {fv}_vals != NULL) {{\n"
2438 ));
2439 out.push_str(&format!(
2440 "{indent} for (size_t {fv}_i = 0; {fv}_i < {fv}_len; {fv}_i++) {{\n"
2441 ));
2442 out.push_str(&format!("{indent} napi_value {fv}_v;\n"));
2443 emit_elem_to_napi(
2444 out,
2445 env,
2446 v,
2447 &format!("{fv}_vals[{fv}_i]"),
2448 &format!("{fv}_v"),
2449 module,
2450 prefix,
2451 structs,
2452 &format!("{indent} "),
2453 );
2454 match k.as_ref() {
2455 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
2456 out.push_str(&format!(
2457 "{indent} napi_set_named_property({env}, {fv}, {fv}_keys[{fv}_i], {fv}_v);\n"
2458 ));
2459 if matches!(k.as_ref(), TypeRef::StringUtf8) {
2460 out.push_str(&format!(
2461 "{indent} weaveffi_free_string((char*){fv}_keys[{fv}_i]);\n"
2462 ));
2463 }
2464 }
2465 other => {
2466 out.push_str(&format!("{indent} napi_value {fv}_k;\n"));
2467 out.push_str(&format!(
2468 "{indent} {}\n",
2469 napi_create_leaf(
2470 env,
2471 other,
2472 &format!("{fv}_keys[{fv}_i]"),
2473 &format!("{fv}_k")
2474 )
2475 ));
2476 out.push_str(&format!(
2477 "{indent} napi_set_property({env}, {fv}, {fv}_k, {fv}_v);\n"
2478 ));
2479 }
2480 }
2481 out.push_str(&format!("{indent} }}\n"));
2482 out.push_str(&format!("{indent} }}\n"));
2483 out.push_str(&format!("{indent}}}\n"));
2484 }
2485 _ => out.push_str(&format!("{indent}napi_get_null({env}, &{fv});\n")),
2486 }
2487}
2488
2489fn emit_ret_to_napi(
2490 out: &mut String,
2491 ty: &TypeRef,
2492 module: &str,
2493 prefix: &str,
2494 fn_name: &str,
2495 structs: &HashMap<String, StructBinding>,
2496) {
2497 out.push_str(" napi_value ret;\n");
2498 match ty {
2499 TypeRef::I32 => out.push_str(" napi_create_int32(env, result, &ret);\n"),
2500 TypeRef::U32 => out.push_str(" napi_create_uint32(env, result, &ret);\n"),
2501 TypeRef::I64 => out.push_str(" napi_create_int64(env, result, &ret);\n"),
2502 TypeRef::F64 => out.push_str(" napi_create_double(env, result, &ret);\n"),
2503 TypeRef::I8 | TypeRef::I16 => out.push_str(" napi_create_int32(env, result, &ret);\n"),
2504 TypeRef::U8 | TypeRef::U16 => out.push_str(" napi_create_uint32(env, result, &ret);\n"),
2505 TypeRef::U64 => out.push_str(" napi_create_int64(env, (int64_t)result, &ret);\n"),
2506 TypeRef::F32 => out.push_str(" napi_create_double(env, result, &ret);\n"),
2507 TypeRef::Bool => out.push_str(" napi_get_boolean(env, result, &ret);\n"),
2508 TypeRef::StringUtf8 => {
2509 out.push_str(" napi_create_string_utf8(env, result, NAPI_AUTO_LENGTH, &ret);\n");
2510 out.push_str(" weaveffi_free_string(result);\n");
2511 }
2512 TypeRef::BorrowedStr => {
2513 out.push_str(" napi_create_string_utf8(env, result, NAPI_AUTO_LENGTH, &ret);\n");
2514 }
2515 TypeRef::TypedHandle(_) | TypeRef::Handle => {
2516 out.push_str(" napi_create_int64(env, (int64_t)(intptr_t)result, &ret);\n");
2517 }
2518 TypeRef::Struct(name) => {
2519 emit_struct_to_object(
2520 out, "env", name, "result", "ret", module, prefix, structs, " ", true,
2521 );
2522 }
2523 TypeRef::Enum(_) => {
2524 out.push_str(" napi_create_int32(env, (int32_t)result, &ret);\n");
2525 }
2526 TypeRef::Bytes => {
2527 out.push_str(" napi_create_buffer_copy(env, out_len, result, NULL, &ret);\n");
2528 out.push_str(" weaveffi_free_bytes((uint8_t*)result, out_len);\n");
2529 }
2530 TypeRef::BorrowedBytes => {
2531 out.push_str(" napi_create_buffer_copy(env, out_len, result, NULL, &ret);\n");
2532 }
2533 TypeRef::Optional(inner) => {
2534 out.push_str(" if (result == NULL) {\n");
2535 out.push_str(" napi_get_null(env, &ret);\n");
2536 out.push_str(" } else {\n");
2537 emit_optional_ret_inner(out, inner, module, prefix, structs);
2538 out.push_str(" }\n");
2539 }
2540 TypeRef::List(inner) => emit_list_ret(out, inner, module, prefix, " ", structs),
2541 TypeRef::Map(_, _) => {
2542 out.push_str(" napi_create_object(env, &ret);\n");
2543 }
2544 TypeRef::Iterator(inner) => {
2545 let fn_pascal = fn_name.to_upper_camel_case();
2546 let iter_type = format!("{prefix}_{module}_{fn_pascal}Iterator");
2547 let et = c_elem_type(inner, module, prefix);
2548 out.push_str(" napi_create_array(env, &ret);\n");
2549 out.push_str(" uint32_t iter_idx = 0;\n");
2550 out.push_str(&format!(" {et} iter_item;\n"));
2551 out.push_str(" weaveffi_error iter_err = {0};\n");
2555 out.push_str(&format!(
2556 " while ({iter_type}_next(result, &iter_item, &iter_err)) {{\n"
2557 ));
2558 out.push_str(" napi_value elem;\n");
2559 match inner.as_ref() {
2560 TypeRef::I32 => {
2561 out.push_str(" napi_create_int32(env, iter_item, &elem);\n");
2562 }
2563 TypeRef::U32 => {
2564 out.push_str(" napi_create_uint32(env, iter_item, &elem);\n");
2565 }
2566 TypeRef::I64 => {
2567 out.push_str(" napi_create_int64(env, iter_item, &elem);\n");
2568 }
2569 TypeRef::F64 => {
2570 out.push_str(" napi_create_double(env, iter_item, &elem);\n");
2571 }
2572 TypeRef::I8 | TypeRef::I16 => {
2573 out.push_str(" napi_create_int32(env, iter_item, &elem);\n");
2574 }
2575 TypeRef::U8 | TypeRef::U16 => {
2576 out.push_str(" napi_create_uint32(env, iter_item, &elem);\n");
2577 }
2578 TypeRef::U64 => {
2579 out.push_str(" napi_create_int64(env, (int64_t)iter_item, &elem);\n");
2580 }
2581 TypeRef::F32 => {
2582 out.push_str(" napi_create_double(env, iter_item, &elem);\n");
2583 }
2584 TypeRef::Bool => {
2585 out.push_str(" napi_get_boolean(env, iter_item, &elem);\n");
2586 }
2587 TypeRef::TypedHandle(_) | TypeRef::Handle => {
2588 out.push_str(
2589 " napi_create_int64(env, (int64_t)(intptr_t)iter_item, &elem);\n",
2590 );
2591 }
2592 TypeRef::StringUtf8 => {
2593 out.push_str(
2594 " napi_create_string_utf8(env, iter_item, NAPI_AUTO_LENGTH, &elem);\n",
2595 );
2596 out.push_str(" weaveffi_free_string(iter_item);\n");
2597 }
2598 TypeRef::Struct(_) | TypeRef::Enum(_) => {
2599 out.push_str(
2600 " napi_create_int64(env, (int64_t)(intptr_t)iter_item, &elem);\n",
2601 );
2602 }
2603 _ => {
2604 out.push_str(" napi_create_int64(env, (int64_t)iter_item, &elem);\n");
2605 }
2606 }
2607 out.push_str(" napi_set_element(env, ret, iter_idx++, elem);\n");
2608 out.push_str(" }\n");
2609 out.push_str(&format!(" {iter_type}_destroy(result);\n"));
2610 }
2611 }
2612 out.push_str(" return ret;\n");
2613}
2614
2615fn emit_optional_ret_inner(
2616 out: &mut String,
2617 inner: &TypeRef,
2618 module: &str,
2619 prefix: &str,
2620 structs: &HashMap<String, StructBinding>,
2621) {
2622 match inner {
2623 TypeRef::I32 => {
2624 out.push_str(" napi_create_int32(env, *result, &ret);\n");
2625 out.push_str(" free(result);\n");
2626 }
2627 TypeRef::U32 => {
2628 out.push_str(" napi_create_uint32(env, *result, &ret);\n");
2629 out.push_str(" free(result);\n");
2630 }
2631 TypeRef::I64 => {
2632 out.push_str(" napi_create_int64(env, *result, &ret);\n");
2633 out.push_str(" free(result);\n");
2634 }
2635 TypeRef::F64 => {
2636 out.push_str(" napi_create_double(env, *result, &ret);\n");
2637 out.push_str(" free(result);\n");
2638 }
2639 TypeRef::I8 | TypeRef::I16 => {
2640 out.push_str(" napi_create_int32(env, *result, &ret);\n");
2641 out.push_str(" free(result);\n");
2642 }
2643 TypeRef::U8 | TypeRef::U16 => {
2644 out.push_str(" napi_create_uint32(env, *result, &ret);\n");
2645 out.push_str(" free(result);\n");
2646 }
2647 TypeRef::U64 => {
2648 out.push_str(" napi_create_int64(env, (int64_t)*result, &ret);\n");
2649 out.push_str(" free(result);\n");
2650 }
2651 TypeRef::F32 => {
2652 out.push_str(" napi_create_double(env, *result, &ret);\n");
2653 out.push_str(" free(result);\n");
2654 }
2655 TypeRef::Bool => {
2656 out.push_str(" napi_get_boolean(env, *result, &ret);\n");
2657 out.push_str(" free(result);\n");
2658 }
2659 TypeRef::TypedHandle(_) | TypeRef::Handle => {
2660 out.push_str(" napi_create_int64(env, (int64_t)(intptr_t)*result, &ret);\n");
2661 out.push_str(" free(result);\n");
2662 }
2663 TypeRef::Enum(_) => {
2664 out.push_str(" napi_create_int32(env, (int32_t)*result, &ret);\n");
2665 out.push_str(" free(result);\n");
2666 }
2667 TypeRef::StringUtf8 => {
2668 out.push_str(" napi_create_string_utf8(env, result, NAPI_AUTO_LENGTH, &ret);\n");
2669 out.push_str(" weaveffi_free_string(result);\n");
2670 }
2671 TypeRef::Struct(name) => {
2672 emit_struct_to_object(
2673 out, "env", name, "result", "ret", module, prefix, structs, " ", true,
2674 );
2675 }
2676 TypeRef::List(li) => emit_list_ret(out, li, module, prefix, " ", structs),
2677 _ => out.push_str(" napi_get_null(env, &ret);\n"),
2678 }
2679}
2680
2681fn emit_list_ret(
2682 out: &mut String,
2683 inner: &TypeRef,
2684 module: &str,
2685 prefix: &str,
2686 ind: &str,
2687 structs: &HashMap<String, StructBinding>,
2688) {
2689 out.push_str(&format!(
2690 "{ind}napi_create_array_with_length(env, out_len, &ret);\n"
2691 ));
2692 out.push_str(&format!(
2693 "{ind}for (size_t ret_i = 0; ret_i < out_len; ret_i++) {{\n"
2694 ));
2695 out.push_str(&format!("{ind} napi_value elem;\n"));
2696 match inner {
2697 TypeRef::I32 => out.push_str(&format!(
2698 "{ind} napi_create_int32(env, result[ret_i], &elem);\n"
2699 )),
2700 TypeRef::U32 => out.push_str(&format!(
2701 "{ind} napi_create_uint32(env, result[ret_i], &elem);\n"
2702 )),
2703 TypeRef::I64 => out.push_str(&format!(
2704 "{ind} napi_create_int64(env, result[ret_i], &elem);\n"
2705 )),
2706 TypeRef::F64 => out.push_str(&format!(
2707 "{ind} napi_create_double(env, result[ret_i], &elem);\n"
2708 )),
2709 TypeRef::I8 | TypeRef::I16 => out.push_str(&format!(
2710 "{ind} napi_create_int32(env, result[ret_i], &elem);\n"
2711 )),
2712 TypeRef::U8 | TypeRef::U16 => out.push_str(&format!(
2713 "{ind} napi_create_uint32(env, result[ret_i], &elem);\n"
2714 )),
2715 TypeRef::U64 => out.push_str(&format!(
2716 "{ind} napi_create_int64(env, (int64_t)result[ret_i], &elem);\n"
2717 )),
2718 TypeRef::F32 => out.push_str(&format!(
2719 "{ind} napi_create_double(env, result[ret_i], &elem);\n"
2720 )),
2721 TypeRef::Bool => out.push_str(&format!(
2722 "{ind} napi_get_boolean(env, result[ret_i], &elem);\n"
2723 )),
2724 TypeRef::TypedHandle(_) | TypeRef::Handle => out.push_str(&format!(
2725 "{ind} napi_create_int64(env, (int64_t)(intptr_t)result[ret_i], &elem);\n"
2726 )),
2727 TypeRef::StringUtf8 => {
2728 out.push_str(&format!(
2729 "{ind} napi_create_string_utf8(env, result[ret_i], NAPI_AUTO_LENGTH, &elem);\n"
2730 ));
2731 out.push_str(&format!("{ind} weaveffi_free_string(result[ret_i]);\n"));
2732 }
2733 TypeRef::Enum(_) => out.push_str(&format!(
2734 "{ind} napi_create_int32(env, (int32_t)result[ret_i], &elem);\n"
2735 )),
2736 TypeRef::Struct(name) => {
2737 let elem_indent = format!("{ind} ");
2738 emit_struct_to_object(
2739 out,
2740 "env",
2741 name,
2742 "result[ret_i]",
2743 "elem",
2744 module,
2745 prefix,
2746 structs,
2747 &elem_indent,
2748 true,
2749 );
2750 }
2751 _ => out.push_str(&format!(
2752 "{ind} napi_create_int64(env, (int64_t)result[ret_i], &elem);\n"
2753 )),
2754 }
2755 out.push_str(&format!(
2756 "{ind} napi_set_element(env, ret, (uint32_t)ret_i, elem);\n"
2757 ));
2758 out.push_str(&format!("{ind}}}\n"));
2759 out.push_str(&format!("{ind}free(result);\n"));
2760}
2761
2762fn ts_type_for(ty: &TypeRef) -> String {
2763 match ty {
2764 TypeRef::I8
2765 | TypeRef::I16
2766 | TypeRef::U8
2767 | TypeRef::U16
2768 | TypeRef::I32
2769 | TypeRef::U32
2770 | TypeRef::I64
2771 | TypeRef::U64
2772 | TypeRef::F32
2773 | TypeRef::F64 => "number".into(),
2774 TypeRef::Bool => "boolean".into(),
2775 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "string".into(),
2776 TypeRef::Bytes | TypeRef::BorrowedBytes => "Buffer".into(),
2777 TypeRef::Handle => "bigint".into(),
2778 TypeRef::TypedHandle(name) => local_type_name(name).to_string(),
2783 TypeRef::Struct(name) => local_type_name(name).to_string(),
2784 TypeRef::Enum(name) => local_type_name(name).to_string(),
2785 TypeRef::Optional(inner) => format!("{} | null", ts_type_for(inner)),
2786 TypeRef::List(inner) => {
2787 let inner_ts = ts_type_for(inner);
2788 if matches!(inner.as_ref(), TypeRef::Optional(_)) {
2789 format!("({inner_ts})[]")
2790 } else {
2791 format!("{inner_ts}[]")
2792 }
2793 }
2794 TypeRef::Map(k, v) => format!("Record<{}, {}>", ts_type_for(k), ts_type_for(v)),
2795 TypeRef::Iterator(inner) => {
2796 let t = ts_type_for(inner);
2797 format!("{t}[]")
2798 }
2799 }
2800}
2801
2802fn emit_doc(out: &mut String, doc: &Option<String>, indent: &str) {
2805 common_emit_doc(out, doc, indent, DocCommentStyle::Javadoc);
2806}
2807
2808fn emit_fn_doc(
2811 out: &mut String,
2812 doc: &Option<String>,
2813 params: &[ParamBinding],
2814 indent: &str,
2815 extra_tags: &[String],
2816) {
2817 let has_param_docs = params.iter().any(|p| p.doc.is_some());
2818 let trimmed_doc = doc.as_ref().map(|d| d.trim()).filter(|d| !d.is_empty());
2819 if trimmed_doc.is_none() && !has_param_docs && extra_tags.is_empty() {
2820 return;
2821 }
2822 out.push_str(indent);
2823 out.push_str("/**\n");
2824 if let Some(d) = trimmed_doc {
2825 for line in d.lines() {
2826 out.push_str(indent);
2827 if line.is_empty() {
2828 out.push_str(" *\n");
2829 } else {
2830 out.push_str(" * ");
2831 out.push_str(line);
2832 out.push('\n');
2833 }
2834 }
2835 }
2836 for p in params {
2837 if let Some(pdoc) = &p.doc {
2838 let pdoc = pdoc.trim();
2839 if pdoc.is_empty() {
2840 continue;
2841 }
2842 let mut lines = pdoc.lines();
2843 if let Some(first) = lines.next() {
2844 out.push_str(indent);
2845 out.push_str(&format!(" * @param {} {}\n", p.name, first));
2846 }
2847 for line in lines {
2848 out.push_str(indent);
2849 if line.is_empty() {
2850 out.push_str(" *\n");
2851 } else {
2852 out.push_str(" * ");
2853 out.push_str(line);
2854 out.push('\n');
2855 }
2856 }
2857 }
2858 }
2859 for tag in extra_tags {
2860 out.push_str(indent);
2861 out.push_str(" * ");
2862 out.push_str(tag);
2863 out.push('\n');
2864 }
2865 out.push_str(indent);
2866 out.push_str(" */\n");
2867}
2868
2869fn render_rich_enum_dts(out: &mut String, e: &EnumBinding) {
2874 let Some(rich) = &e.rich else {
2875 return;
2876 };
2877 let name = &e.name;
2878 emit_doc(out, &e.doc, "");
2879 out.push_str(&format!("export class {name} {{\n"));
2880 for v in &rich.variants {
2881 let factory = v.name.to_lower_camel_case();
2882 let params: Vec<String> = v
2883 .fields
2884 .iter()
2885 .map(|f| format!("{}: {}", f.name, ts_type_for(&f.ty)))
2886 .collect();
2887 emit_doc(out, &v.doc, " ");
2888 out.push_str(&format!(
2889 " static {factory}({}): {name};\n",
2890 params.join(", ")
2891 ));
2892 }
2893 out.push_str(" /** The active variant's discriminant. */\n");
2894 out.push_str(" tag(): number;\n");
2895 for v in &rich.variants {
2896 for f in &v.fields {
2897 let getter = format!(
2898 "{}{}",
2899 v.name.to_lower_camel_case(),
2900 f.name.to_upper_camel_case()
2901 );
2902 emit_doc(out, &f.doc, " ");
2903 out.push_str(&format!(" get {getter}(): {};\n", ts_type_for(&f.ty)));
2904 }
2905 }
2906 out.push_str(" /** Free the underlying native object. */\n");
2907 out.push_str(" destroy(): void;\n");
2908 out.push_str("}\n");
2909 out.push_str(&format!("export namespace {name} {{\n"));
2911 out.push_str(" const Tag: Readonly<{\n");
2912 for v in &e.variants {
2913 out.push_str(&format!(" {}: {},\n", v.name, v.value));
2914 }
2915 out.push_str(" }>;\n");
2916 out.push_str("}\n");
2917}
2918
2919fn render_struct_builder_dts(out: &mut String, s: &StructBinding) {
2920 let name = &s.name;
2921 emit_doc(out, &s.doc, "");
2922 out.push_str(&format!("export interface {}Builder {{\n", s.name));
2923 for field in &s.fields {
2924 let method = format!("with{}", field.name.to_upper_camel_case());
2925 let ts = ts_type_for(&field.ty);
2926 emit_doc(out, &field.doc, " ");
2927 out.push_str(&format!(" {method}(value: {ts}): {name}Builder;\n"));
2928 }
2929 out.push_str(&format!(" build(): {name};\n"));
2930 out.push_str("}\n");
2931}
2932
2933fn rich_enum_names(model: &BindingModel) -> HashSet<String> {
2937 model
2938 .modules
2939 .iter()
2940 .flat_map(|m| m.enums.iter())
2941 .filter(|e| e.is_rich())
2942 .map(|e| e.name.clone())
2943 .collect()
2944}
2945
2946fn rich_struct_ref(ty: &TypeRef, rich: &HashSet<String>) -> Option<(String, bool)> {
2950 match ty {
2951 TypeRef::Struct(n) if rich.contains(local_type_name(n)) => {
2952 Some((local_type_name(n).to_string(), false))
2953 }
2954 TypeRef::Optional(inner) => match inner.as_ref() {
2955 TypeRef::Struct(n) if rich.contains(local_type_name(n)) => {
2956 Some((local_type_name(n).to_string(), true))
2957 }
2958 _ => None,
2959 },
2960 _ => None,
2961 }
2962}
2963
2964fn render_node_index(api: &Api, prefix: &str, strip: bool, input_basename: &str) -> String {
2972 let model = BindingModel::build(api, prefix);
2973 let dbl = CommentStyle::DoubleSlash;
2974 let mut out = render_prelude(dbl, input_basename);
2975 out.push_str(
2976 "// Prefer the default node-gyp output path; fall back to a\n\
2977 // prebuilt index.node placed next to this file.\n\
2978 let addon;\n\
2979 try {\n addon = require('./build/Release/weaveffi.node');\n} catch (e) {\n addon = require('./index.node');\n}\n",
2980 );
2981
2982 let rich = rich_enum_names(&model);
2983 if rich.is_empty() {
2984 out.push_str("module.exports = addon;\n\n");
2985 out.push_str(&render_trailer(dbl, "index.js"));
2986 return out;
2987 }
2988
2989 out.push_str(
2992 "\n// Re-export every native binding, then layer idiomatic wrappers for\n\
2993 // rich (algebraic) enums on top.\n\
2994 const wv = {};\n\
2995 for (const _name of Object.getOwnPropertyNames(addon)) {\n wv[_name] = addon[_name];\n}\n\n",
2996 );
2997
2998 for m in &model.modules {
2999 for e in &m.enums {
3000 if e.is_rich() {
3001 render_rich_enum_class_js(&mut out, e, &m.path, strip);
3002 }
3003 }
3004 }
3005
3006 for m in &model.modules {
3009 for f in &m.functions {
3010 if f.is_async {
3011 continue;
3012 }
3013 let ret_rich = f.ret.as_ref().and_then(|r| rich_struct_ref(r, &rich));
3014 let param_rich: Vec<Option<(String, bool)>> = f
3015 .params
3016 .iter()
3017 .map(|p| rich_struct_ref(&p.ty, &rich))
3018 .collect();
3019 if ret_rich.is_none() && param_rich.iter().all(Option::is_none) {
3020 continue;
3021 }
3022 let js = wrapper_name(&m.path, &f.name, strip);
3023 let param_names: Vec<String> = f.params.iter().map(|p| p.name.clone()).collect();
3024 let call_args: Vec<String> = f
3025 .params
3026 .iter()
3027 .zip(¶m_rich)
3028 .map(|(p, r)| match r {
3029 Some((en, _)) => {
3030 format!("{n} instanceof {en} ? {n}._handle : {n}", n = p.name)
3031 }
3032 None => p.name.clone(),
3033 })
3034 .collect();
3035 let inner = format!("addon.{js}({})", call_args.join(", "));
3036 out.push_str(&format!(
3037 "wv.{js} = function ({}) {{\n",
3038 param_names.join(", ")
3039 ));
3040 match ret_rich {
3041 Some((en, false)) => {
3042 out.push_str(&format!(" return new {en}({inner});\n"));
3043 }
3044 Some((en, true)) => {
3045 out.push_str(&format!(" const _r = {inner};\n"));
3046 out.push_str(&format!(" return _r == null ? null : new {en}(_r);\n"));
3047 }
3048 None => {
3049 out.push_str(&format!(" return {inner};\n"));
3050 }
3051 }
3052 out.push_str("};\n");
3053 }
3054 }
3055
3056 out.push_str("\nmodule.exports = wv;\n\n");
3057 out.push_str(&render_trailer(dbl, "index.js"));
3058 out
3059}
3060
3061fn render_rich_enum_class_js(out: &mut String, e: &EnumBinding, module: &str, strip: bool) {
3065 let Some(rich) = &e.rich else {
3066 return;
3067 };
3068 let name = &e.name;
3069 let destroy_js = wrapper_name(module, &rich_destroy_base(name), strip);
3070
3071 out.push_str(&format!("class {name} {{\n"));
3072 out.push_str(" constructor(handle) {\n");
3073 out.push_str(" this._handle = handle;\n");
3074 out.push_str(&format!(
3075 " {name}._cleanup.register(this, handle, this);\n"
3076 ));
3077 out.push_str(" }\n");
3078
3079 for v in &rich.variants {
3081 let factory = v.name.to_lower_camel_case();
3082 let ctor_js = wrapper_name(module, &rich_ctor_base(name, &v.name), strip);
3083 let params: Vec<String> = v.fields.iter().map(|f| f.name.clone()).collect();
3084 let joined = params.join(", ");
3085 out.push_str(&format!(
3086 " static {factory}({joined}) {{\n return new {name}(addon.{ctor_js}({joined}));\n }}\n"
3087 ));
3088 }
3089
3090 let tag_js = wrapper_name(module, &rich_tag_base(name), strip);
3092 out.push_str(&format!(
3093 " tag() {{\n return addon.{tag_js}(this._handle);\n }}\n"
3094 ));
3095
3096 for v in &rich.variants {
3098 for f in &v.fields {
3099 let getter = format!(
3100 "{}{}",
3101 v.name.to_lower_camel_case(),
3102 f.name.to_upper_camel_case()
3103 );
3104 let getter_js = wrapper_name(module, &rich_getter_base(name, &v.name, &f.name), strip);
3105 out.push_str(&format!(
3106 " get {getter}() {{\n return addon.{getter_js}(this._handle);\n }}\n"
3107 ));
3108 }
3109 }
3110
3111 out.push_str(" destroy() {\n");
3114 out.push_str(" if (this._handle) {\n");
3115 out.push_str(&format!(" {name}._cleanup.unregister(this);\n"));
3116 out.push_str(&format!(" addon.{destroy_js}(this._handle);\n"));
3117 out.push_str(" this._handle = 0;\n");
3118 out.push_str(" }\n");
3119 out.push_str(" }\n");
3120 out.push_str("}\n");
3121
3122 out.push_str(&format!(
3123 "{name}._cleanup = new FinalizationRegistry((handle) => {{\n if (handle) {{ addon.{destroy_js}(handle); }}\n}});\n"
3124 ));
3125
3126 let consts: Vec<String> = e
3128 .variants
3129 .iter()
3130 .map(|v| format!("{}: {}", v.name, v.value))
3131 .collect();
3132 out.push_str(&format!(
3133 "{name}.Tag = Object.freeze({{ {} }});\n",
3134 consts.join(", ")
3135 ));
3136 out.push_str(&format!("wv.{name} = {name};\n\n"));
3137}
3138
3139fn render_node_dts(
3140 api: &Api,
3141 prefix: &str,
3142 strip_module_prefix: bool,
3143 input_basename: &str,
3144) -> String {
3145 let model = BindingModel::build(api, prefix);
3146 let mut out = render_prelude(CommentStyle::DoubleSlash, input_basename);
3147 out.push_str("// Generated types for WeaveFFI functions\n");
3148 for m in &model.modules {
3149 for s in &m.structs {
3150 emit_doc(&mut out, &s.doc, "");
3151 out.push_str(&format!("export interface {} {{\n", s.name));
3152 for field in &s.fields {
3153 emit_doc(&mut out, &field.doc, " ");
3154 out.push_str(&format!(" {}: {};\n", field.name, ts_type_for(&field.ty)));
3155 }
3156 out.push_str("}\n");
3157 if s.builder.is_some() {
3158 render_struct_builder_dts(&mut out, s);
3159 }
3160 }
3161 for e in &m.enums {
3162 if e.is_rich() {
3165 render_rich_enum_dts(&mut out, e);
3166 continue;
3167 }
3168 emit_doc(&mut out, &e.doc, "");
3169 out.push_str(&format!("export enum {} {{\n", e.name));
3170 for v in &e.variants {
3171 emit_doc(&mut out, &v.doc, " ");
3172 out.push_str(&format!(" {} = {},\n", v.name, v.value));
3173 }
3174 out.push_str("}\n");
3175 }
3176 out.push_str(&format!("// module {}\n", m.path));
3177 for l in &m.listeners {
3178 let Some(cb) = m.callback(&l.event_callback) else {
3179 continue;
3180 };
3181 let cb_params: Vec<String> = cb
3182 .params
3183 .iter()
3184 .map(|p| format!("{}: {}", p.name, ts_type_for(&p.ty)))
3185 .collect();
3186 let register = wrapper_name(
3187 &m.path,
3188 &format!("register_{}", l.name),
3189 strip_module_prefix,
3190 );
3191 let unregister = wrapper_name(
3192 &m.path,
3193 &format!("unregister_{}", l.name),
3194 strip_module_prefix,
3195 );
3196 emit_doc(&mut out, &l.doc, "");
3197 out.push_str(&format!(
3198 "export function {register}(callback: ({}) => void): number\n",
3199 cb_params.join(", ")
3200 ));
3201 out.push_str(&format!("export function {unregister}(id: number): void\n"));
3202 }
3203 for f in &m.functions {
3204 let params: Vec<String> = f
3205 .params
3206 .iter()
3207 .map(|p| format!("{}: {}", p.name, ts_type_for(&p.ty)))
3208 .collect();
3209 let base_ret = match &f.ret {
3210 Some(ty) => ts_type_for(ty),
3211 None => "void".into(),
3212 };
3213 let ret = if f.is_async {
3214 format!("Promise<{base_ret}>")
3215 } else {
3216 base_ret
3217 };
3218 let ts_name = wrapper_name(&m.path, &f.name, strip_module_prefix);
3219 let mut tags = vec![format!("Maps to C function: {}", f.c_base)];
3220 if let Some(msg) = &f.deprecated {
3221 tags.push(format!("@deprecated {}", msg));
3222 }
3223 emit_fn_doc(&mut out, &f.doc, &f.params, "", &tags);
3224 out.push_str(&format!(
3225 "export function {}({}): {}\n",
3226 ts_name,
3227 params.join(", "),
3228 ret
3229 ));
3230 }
3231 }
3232 out.push('\n');
3233 out.push_str(&render_trailer(CommentStyle::DoubleSlash, "types.d.ts"));
3234 out
3235}
3236
3237#[cfg(test)]
3238mod tests {
3239 use super::*;
3240 use weaveffi_core::codegen::Generator;
3241 use weaveffi_ir::ir::{EnumDef, EnumVariant, Function, Module, Param, StructDef, StructField};
3242
3243 fn make_api(modules: Vec<Module>) -> Api {
3244 Api {
3245 version: "0.4.0".into(),
3246 modules,
3247 generators: None,
3248 package: None,
3249 }
3250 }
3251
3252 fn make_module(name: &str) -> Module {
3253 Module {
3254 name: name.into(),
3255 functions: vec![],
3256 structs: vec![],
3257 enums: vec![],
3258 callbacks: vec![],
3259 listeners: vec![],
3260 errors: None,
3261 modules: vec![],
3262 }
3263 }
3264
3265 #[test]
3266 fn listeners_generate_tsfn_register_unregister() {
3267 use weaveffi_ir::ir::{CallbackDef, ListenerDef};
3268 let api = make_api(vec![Module {
3269 name: "events".into(),
3270 functions: vec![],
3271 structs: vec![],
3272 enums: vec![],
3273 callbacks: vec![CallbackDef {
3274 name: "OnMessage".into(),
3275 doc: None,
3276 params: vec![Param {
3277 name: "message".into(),
3278 ty: TypeRef::StringUtf8,
3279 mutable: false,
3280 doc: None,
3281 }],
3282 }],
3283 listeners: vec![ListenerDef {
3284 name: "message_listener".into(),
3285 event_callback: "OnMessage".into(),
3286 doc: None,
3287 }],
3288 errors: None,
3289 modules: vec![],
3290 }]);
3291 let dir = tempfile::tempdir().unwrap();
3292 let out = Utf8Path::from_path(dir.path()).unwrap();
3293 NodeGenerator
3294 .generate(&api, out, &NodeConfig::default())
3295 .unwrap();
3296 let addon = std::fs::read_to_string(dir.path().join("node/weaveffi_addon.c")).unwrap();
3297 assert!(
3298 addon.contains("napi_create_threadsafe_function"),
3299 "listeners must use threadsafe functions: {addon}"
3300 );
3301 assert!(
3302 addon.contains("Napi_weaveffi_events_register_message_listener"),
3303 "register N-API fn missing: {addon}"
3304 );
3305 assert!(
3306 addon.contains("Napi_weaveffi_events_unregister_message_listener"),
3307 "unregister N-API fn missing: {addon}"
3308 );
3309 assert!(
3310 addon.contains("napi_call_threadsafe_function(ctx->tsfn, p, napi_tsfn_nonblocking)"),
3311 "trampoline must queue payloads: {addon}"
3312 );
3313 assert!(
3314 addon.contains("napi_unref_threadsafe_function"),
3315 "tsfn must be unref'd so listeners don't pin the loop: {addon}"
3316 );
3317 let dts = std::fs::read_to_string(dir.path().join("node/types.d.ts")).unwrap();
3318 assert!(
3319 dts.contains(
3320 "export function events_register_message_listener(callback: (message: string) => void): number"
3321 ),
3322 "register dts missing: {dts}"
3323 );
3324 assert!(
3325 dts.contains("export function events_unregister_message_listener(id: number): void"),
3326 "unregister dts missing: {dts}"
3327 );
3328 }
3329
3330 #[test]
3331 fn ts_type_for_primitives() {
3332 assert_eq!(ts_type_for(&TypeRef::I32), "number");
3333 assert_eq!(ts_type_for(&TypeRef::Bool), "boolean");
3334 assert_eq!(ts_type_for(&TypeRef::StringUtf8), "string");
3335 assert_eq!(ts_type_for(&TypeRef::Bytes), "Buffer");
3336 assert_eq!(ts_type_for(&TypeRef::Handle), "bigint");
3337 }
3338
3339 #[test]
3340 fn ts_type_for_struct_and_enum() {
3341 assert_eq!(ts_type_for(&TypeRef::Struct("Contact".into())), "Contact");
3342 assert_eq!(ts_type_for(&TypeRef::Enum("Color".into())), "Color");
3343 assert_eq!(
3344 ts_type_for(&TypeRef::TypedHandle("Contact".into())),
3345 "Contact"
3346 );
3347 }
3348
3349 #[test]
3350 fn ts_type_for_cross_module_uses_local_name() {
3351 assert_eq!(
3354 ts_type_for(&TypeRef::TypedHandle("kv.Store".into())),
3355 "Store"
3356 );
3357 assert_eq!(ts_type_for(&TypeRef::Struct("kv.Store".into())), "Store");
3358 assert_eq!(ts_type_for(&TypeRef::Enum("kv.Kind".into())), "Kind");
3359 }
3360
3361 #[test]
3362 fn ts_type_for_optional() {
3363 let ty = TypeRef::Optional(Box::new(TypeRef::StringUtf8));
3364 assert_eq!(ts_type_for(&ty), "string | null");
3365 }
3366
3367 #[test]
3368 fn ts_type_for_list() {
3369 let ty = TypeRef::List(Box::new(TypeRef::I32));
3370 assert_eq!(ts_type_for(&ty), "number[]");
3371 }
3372
3373 #[test]
3374 fn ts_type_for_list_of_optional() {
3375 let ty = TypeRef::List(Box::new(TypeRef::Optional(Box::new(TypeRef::I32))));
3376 assert_eq!(ts_type_for(&ty), "(number | null)[]");
3377 }
3378
3379 #[test]
3380 fn ts_type_for_map() {
3381 let ty = TypeRef::Map(Box::new(TypeRef::StringUtf8), Box::new(TypeRef::I32));
3382 assert_eq!(ts_type_for(&ty), "Record<string, number>");
3383 }
3384
3385 #[test]
3386 fn ts_type_for_optional_list() {
3387 let ty = TypeRef::Optional(Box::new(TypeRef::List(Box::new(TypeRef::I32))));
3388 assert_eq!(ts_type_for(&ty), "number[] | null");
3389 }
3390
3391 #[test]
3392 fn generate_node_dts_with_structs() {
3393 let mut m = make_module("contacts");
3394 m.structs.push(StructDef {
3395 name: "Contact".into(),
3396 doc: None,
3397 fields: vec![
3398 StructField {
3399 name: "name".into(),
3400 ty: TypeRef::StringUtf8,
3401 doc: None,
3402 default: None,
3403 },
3404 StructField {
3405 name: "age".into(),
3406 ty: TypeRef::I32,
3407 doc: None,
3408 default: None,
3409 },
3410 StructField {
3411 name: "active".into(),
3412 ty: TypeRef::Bool,
3413 doc: None,
3414 default: None,
3415 },
3416 ],
3417 builder: false,
3418 });
3419 m.enums.push(EnumDef {
3420 name: "Color".into(),
3421 doc: None,
3422 variants: vec![
3423 EnumVariant {
3424 name: "Red".into(),
3425 value: 0,
3426 doc: None,
3427 fields: vec![],
3428 },
3429 EnumVariant {
3430 name: "Green".into(),
3431 value: 1,
3432 doc: None,
3433 fields: vec![],
3434 },
3435 EnumVariant {
3436 name: "Blue".into(),
3437 value: 2,
3438 doc: None,
3439 fields: vec![],
3440 },
3441 ],
3442 });
3443 m.functions.push(Function {
3444 name: "get_contact".into(),
3445 params: vec![Param {
3446 name: "id".into(),
3447 ty: TypeRef::I32,
3448 mutable: false,
3449 doc: None,
3450 }],
3451 returns: Some(TypeRef::Optional(Box::new(TypeRef::Struct(
3452 "Contact".into(),
3453 )))),
3454 doc: None,
3455 r#async: false,
3456 cancellable: false,
3457 deprecated: None,
3458 since: None,
3459 });
3460 m.functions.push(Function {
3461 name: "list_contacts".into(),
3462 params: vec![],
3463 returns: Some(TypeRef::List(Box::new(TypeRef::Struct("Contact".into())))),
3464 doc: None,
3465 r#async: false,
3466 cancellable: false,
3467 deprecated: None,
3468 since: None,
3469 });
3470
3471 let dts = render_node_dts(&make_api(vec![m]), "weaveffi", true, "weaveffi.yml");
3472
3473 assert!(dts.contains("export interface Contact {"));
3474 assert!(dts.contains(" name: string;"));
3475 assert!(dts.contains(" age: number;"));
3476 assert!(dts.contains(" active: boolean;"));
3477 assert!(dts.contains("export enum Color {"));
3478 assert!(dts.contains(" Red = 0,"));
3479 assert!(dts.contains(" Green = 1,"));
3480 assert!(dts.contains(" Blue = 2,"));
3481 assert!(dts.contains("export function get_contact(id: number): Contact | null"));
3482 assert!(dts.contains("export function list_contacts(): Contact[]"));
3483
3484 let iface_pos = dts.find("export interface Contact").unwrap();
3485 let enum_pos = dts.find("export enum Color").unwrap();
3486 let fn_pos = dts.find("export function get_contact").unwrap();
3487 assert!(
3488 iface_pos < fn_pos,
3489 "interface should appear before functions"
3490 );
3491 assert!(enum_pos < fn_pos, "enum should appear before functions");
3492 }
3493
3494 #[test]
3495 fn node_generates_binding_gyp() {
3496 let api = make_api(vec![{
3497 let mut m = make_module("math");
3498 m.functions.push(Function {
3499 name: "add".into(),
3500 params: vec![
3501 Param {
3502 name: "a".into(),
3503 ty: TypeRef::I32,
3504 mutable: false,
3505 doc: None,
3506 },
3507 Param {
3508 name: "b".into(),
3509 ty: TypeRef::I32,
3510 mutable: false,
3511 doc: None,
3512 },
3513 ],
3514 returns: Some(TypeRef::I32),
3515 doc: None,
3516 r#async: false,
3517 cancellable: false,
3518 deprecated: None,
3519 since: None,
3520 });
3521 m
3522 }]);
3523
3524 let tmp = std::env::temp_dir().join("weaveffi_test_node_binding_gyp");
3525 let _ = std::fs::remove_dir_all(&tmp);
3526 std::fs::create_dir_all(&tmp).unwrap();
3527 let out_dir = Utf8Path::from_path(&tmp).expect("temp dir is valid UTF-8");
3528
3529 NodeGenerator
3530 .generate(&api, out_dir, &NodeConfig::default())
3531 .unwrap();
3532
3533 let gyp = std::fs::read_to_string(tmp.join("node").join("binding.gyp")).unwrap();
3534 assert!(
3535 gyp.contains("\"target_name\": \"weaveffi\""),
3536 "missing target_name: {gyp}"
3537 );
3538 assert!(
3539 gyp.contains("weaveffi_addon.c"),
3540 "missing source file: {gyp}"
3541 );
3542
3543 let addon = std::fs::read_to_string(tmp.join("node").join("weaveffi_addon.c")).unwrap();
3544 assert!(
3545 addon.contains("napi_value Init("),
3546 "missing Init function: {addon}"
3547 );
3548 assert!(
3549 addon.contains("weaveffi_math_add"),
3550 "missing C ABI call: {addon}"
3551 );
3552 assert!(
3553 addon.contains("napi_get_cb_info"),
3554 "missing napi_get_cb_info call: {addon}"
3555 );
3556
3557 let pkg = std::fs::read_to_string(tmp.join("node").join("package.json")).unwrap();
3558 assert!(pkg.contains("\"gypfile\": true"), "missing gypfile: {pkg}");
3559 assert!(
3560 pkg.contains("node-gyp rebuild"),
3561 "missing install script: {pkg}"
3562 );
3563
3564 let _ = std::fs::remove_dir_all(&tmp);
3565 }
3566
3567 #[test]
3568 fn generate_node_dts_with_structs_and_enums() {
3569 let api = make_api(vec![Module {
3570 name: "contacts".to_string(),
3571 functions: vec![
3572 Function {
3573 name: "get_contact".to_string(),
3574 params: vec![Param {
3575 name: "id".to_string(),
3576 ty: TypeRef::I32,
3577 mutable: false,
3578 doc: None,
3579 }],
3580 returns: Some(TypeRef::Optional(Box::new(TypeRef::Struct(
3581 "Contact".into(),
3582 )))),
3583 doc: None,
3584 r#async: false,
3585 cancellable: false,
3586 deprecated: None,
3587 since: None,
3588 },
3589 Function {
3590 name: "list_contacts".to_string(),
3591 params: vec![],
3592 returns: Some(TypeRef::List(Box::new(TypeRef::Struct("Contact".into())))),
3593 doc: None,
3594 r#async: false,
3595 cancellable: false,
3596 deprecated: None,
3597 since: None,
3598 },
3599 Function {
3600 name: "set_favorite_color".to_string(),
3601 params: vec![
3602 Param {
3603 name: "contact_id".to_string(),
3604 ty: TypeRef::I32,
3605 mutable: false,
3606 doc: None,
3607 },
3608 Param {
3609 name: "color".to_string(),
3610 ty: TypeRef::Optional(Box::new(TypeRef::Enum("Color".into()))),
3611 mutable: false,
3612 doc: None,
3613 },
3614 ],
3615 returns: None,
3616 doc: None,
3617 r#async: false,
3618 cancellable: false,
3619 deprecated: None,
3620 since: None,
3621 },
3622 Function {
3623 name: "get_tags".to_string(),
3624 params: vec![Param {
3625 name: "contact_id".to_string(),
3626 ty: TypeRef::I32,
3627 mutable: false,
3628 doc: None,
3629 }],
3630 returns: Some(TypeRef::List(Box::new(TypeRef::StringUtf8))),
3631 doc: None,
3632 r#async: false,
3633 cancellable: false,
3634 deprecated: None,
3635 since: None,
3636 },
3637 ],
3638 structs: vec![StructDef {
3639 name: "Contact".to_string(),
3640 doc: None,
3641 fields: vec![
3642 StructField {
3643 name: "name".to_string(),
3644 ty: TypeRef::StringUtf8,
3645 doc: None,
3646 default: None,
3647 },
3648 StructField {
3649 name: "email".to_string(),
3650 ty: TypeRef::Optional(Box::new(TypeRef::StringUtf8)),
3651 doc: None,
3652 default: None,
3653 },
3654 StructField {
3655 name: "tags".to_string(),
3656 ty: TypeRef::List(Box::new(TypeRef::StringUtf8)),
3657 doc: None,
3658 default: None,
3659 },
3660 ],
3661 builder: false,
3662 }],
3663 enums: vec![EnumDef {
3664 name: "Color".to_string(),
3665 doc: None,
3666 variants: vec![
3667 EnumVariant {
3668 name: "Red".to_string(),
3669 value: 0,
3670 doc: None,
3671 fields: vec![],
3672 },
3673 EnumVariant {
3674 name: "Green".to_string(),
3675 value: 1,
3676 doc: None,
3677 fields: vec![],
3678 },
3679 EnumVariant {
3680 name: "Blue".to_string(),
3681 value: 2,
3682 doc: None,
3683 fields: vec![],
3684 },
3685 ],
3686 }],
3687 callbacks: vec![],
3688 listeners: vec![],
3689 errors: None,
3690 modules: vec![],
3691 }]);
3692
3693 let tmp = std::env::temp_dir().join("weaveffi_test_node_structs_and_enums");
3694 let _ = std::fs::remove_dir_all(&tmp);
3695 std::fs::create_dir_all(&tmp).unwrap();
3696 let out_dir = Utf8Path::from_path(&tmp).expect("temp dir is valid UTF-8");
3697
3698 NodeGenerator
3699 .generate(
3700 &api,
3701 out_dir,
3702 &NodeConfig {
3703 strip_module_prefix: true,
3704 ..NodeConfig::default()
3705 },
3706 )
3707 .unwrap();
3708
3709 let dts = std::fs::read_to_string(tmp.join("node").join("types.d.ts")).unwrap();
3710
3711 assert!(
3712 dts.contains("export interface Contact {"),
3713 "missing Contact interface: {dts}"
3714 );
3715 assert!(dts.contains(" name: string;"), "missing name field: {dts}");
3716 assert!(
3717 dts.contains(" email: string | null;"),
3718 "missing optional email field: {dts}"
3719 );
3720 assert!(
3721 dts.contains(" tags: string[];"),
3722 "missing list tags field: {dts}"
3723 );
3724
3725 assert!(
3726 dts.contains("export enum Color {"),
3727 "missing Color enum: {dts}"
3728 );
3729 assert!(dts.contains(" Red = 0,"), "missing Red variant: {dts}");
3730 assert!(dts.contains(" Green = 1,"), "missing Green variant: {dts}");
3731 assert!(dts.contains(" Blue = 2,"), "missing Blue variant: {dts}");
3732
3733 assert!(
3734 dts.contains("export function get_contact(id: number): Contact | null"),
3735 "missing get_contact with optional return: {dts}"
3736 );
3737 assert!(
3738 dts.contains("export function list_contacts(): Contact[]"),
3739 "missing list_contacts with list return: {dts}"
3740 );
3741 assert!(
3742 dts.contains(
3743 "export function set_favorite_color(contact_id: number, color: Color | null): void"
3744 ),
3745 "missing set_favorite_color with optional enum param: {dts}"
3746 );
3747 assert!(
3748 dts.contains("export function get_tags(contact_id: number): string[]"),
3749 "missing get_tags with list return: {dts}"
3750 );
3751
3752 let iface_pos = dts.find("export interface Contact").unwrap();
3753 let enum_pos = dts.find("export enum Color").unwrap();
3754 let fn_pos = dts.find("export function get_contact").unwrap();
3755 assert!(
3756 iface_pos < fn_pos,
3757 "interface should appear before functions"
3758 );
3759 assert!(enum_pos < fn_pos, "enum should appear before functions");
3760
3761 let _ = std::fs::remove_dir_all(&tmp);
3762 }
3763
3764 #[test]
3765 fn node_custom_package_name() {
3766 let api = make_api(vec![make_module("math")]);
3767
3768 let tmp = std::env::temp_dir().join("weaveffi_test_node_custom_pkg");
3769 let _ = std::fs::remove_dir_all(&tmp);
3770 std::fs::create_dir_all(&tmp).unwrap();
3771 let out_dir = Utf8Path::from_path(&tmp).expect("temp dir is valid UTF-8");
3772
3773 let config = NodeConfig {
3774 package_name: Some("@myorg/cool-lib".into()),
3775 ..NodeConfig::default()
3776 };
3777 NodeGenerator.generate(&api, out_dir, &config).unwrap();
3778
3779 let pkg = std::fs::read_to_string(tmp.join("node").join("package.json")).unwrap();
3780 assert!(
3781 pkg.contains("\"name\": \"@myorg/cool-lib\""),
3782 "package.json should use custom name: {pkg}"
3783 );
3784 assert!(
3785 !pkg.contains("\"name\": \"weaveffi\""),
3786 "package.json should not contain default name: {pkg}"
3787 );
3788
3789 let _ = std::fs::remove_dir_all(&tmp);
3790 }
3791
3792 #[test]
3793 fn node_dts_has_jsdoc() {
3794 let api = make_api(vec![{
3795 let mut m = make_module("math");
3796 m.functions.push(Function {
3797 name: "add".into(),
3798 params: vec![
3799 Param {
3800 name: "a".into(),
3801 ty: TypeRef::I32,
3802 mutable: false,
3803 doc: None,
3804 },
3805 Param {
3806 name: "b".into(),
3807 ty: TypeRef::I32,
3808 mutable: false,
3809 doc: None,
3810 },
3811 ],
3812 returns: Some(TypeRef::I32),
3813 doc: None,
3814 r#async: false,
3815 cancellable: false,
3816 deprecated: None,
3817 since: None,
3818 });
3819 m.functions.push(Function {
3820 name: "subtract".into(),
3821 params: vec![
3822 Param {
3823 name: "a".into(),
3824 ty: TypeRef::I32,
3825 mutable: false,
3826 doc: None,
3827 },
3828 Param {
3829 name: "b".into(),
3830 ty: TypeRef::I32,
3831 mutable: false,
3832 doc: None,
3833 },
3834 ],
3835 returns: Some(TypeRef::I32),
3836 doc: None,
3837 r#async: false,
3838 cancellable: false,
3839 deprecated: None,
3840 since: None,
3841 });
3842 m
3843 }]);
3844
3845 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
3846
3847 assert!(
3848 dts.contains("Maps to C function: weaveffi_math_add"),
3849 "missing JSDoc for add: {dts}"
3850 );
3851 assert!(
3852 dts.contains("Maps to C function: weaveffi_math_subtract"),
3853 "missing JSDoc for subtract: {dts}"
3854 );
3855 }
3856
3857 #[test]
3858 fn node_addon_has_no_todo() {
3859 let api = make_api(vec![{
3860 let mut m = make_module("math");
3861 m.functions.push(Function {
3862 name: "add".into(),
3863 params: vec![
3864 Param {
3865 name: "a".into(),
3866 ty: TypeRef::I32,
3867 mutable: false,
3868 doc: None,
3869 },
3870 Param {
3871 name: "b".into(),
3872 ty: TypeRef::I32,
3873 mutable: false,
3874 doc: None,
3875 },
3876 ],
3877 returns: Some(TypeRef::I32),
3878 doc: None,
3879 r#async: false,
3880 cancellable: false,
3881 deprecated: None,
3882 since: None,
3883 });
3884 m
3885 }]);
3886 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3887 assert!(
3888 !addon.contains("// TODO: implement"),
3889 "generated addon.c should not contain TODO comments: {addon}"
3890 );
3891 }
3892
3893 #[test]
3894 fn node_addon_extracts_args() {
3895 let api = make_api(vec![{
3896 let mut m = make_module("math");
3897 m.functions.push(Function {
3898 name: "add".into(),
3899 params: vec![
3900 Param {
3901 name: "a".into(),
3902 ty: TypeRef::I32,
3903 mutable: false,
3904 doc: None,
3905 },
3906 Param {
3907 name: "b".into(),
3908 ty: TypeRef::I32,
3909 mutable: false,
3910 doc: None,
3911 },
3912 ],
3913 returns: Some(TypeRef::I32),
3914 doc: None,
3915 r#async: false,
3916 cancellable: false,
3917 deprecated: None,
3918 since: None,
3919 });
3920 m
3921 }]);
3922 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3923 assert!(
3924 addon.contains("napi_get_cb_info"),
3925 "generated addon.c should call napi_get_cb_info: {addon}"
3926 );
3927 }
3928
3929 #[test]
3930 fn node_addon_frees_strings() {
3931 let api = make_api(vec![{
3932 let mut m = make_module("greet");
3933 m.functions.push(Function {
3934 name: "hello".into(),
3935 params: vec![Param {
3936 name: "name".into(),
3937 ty: TypeRef::StringUtf8,
3938 mutable: false,
3939 doc: None,
3940 }],
3941 returns: Some(TypeRef::StringUtf8),
3942 doc: None,
3943 r#async: false,
3944 cancellable: false,
3945 deprecated: None,
3946 since: None,
3947 });
3948 m
3949 }]);
3950 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3951 assert!(
3952 addon.contains("weaveffi_free_string(result)"),
3953 "generated addon should free returned strings: {addon}"
3954 );
3955 assert!(
3956 addon.contains("#include <string.h>"),
3957 "generated addon should include string.h: {addon}"
3958 );
3959 assert!(
3960 addon.contains("#include <stdlib.h>"),
3961 "generated addon should include stdlib.h: {addon}"
3962 );
3963 assert!(
3964 addon.contains("weaveffi_error_clear(&err)"),
3965 "generated addon should clear errors: {addon}"
3966 );
3967 }
3968
3969 #[test]
3970 fn node_custom_prefix_threads_to_user_symbols() {
3971 let api = make_api(vec![{
3972 let mut m = make_module("greet");
3973 m.functions.push(Function {
3974 name: "hello".into(),
3975 params: vec![Param {
3976 name: "name".into(),
3977 ty: TypeRef::StringUtf8,
3978 mutable: false,
3979 doc: None,
3980 }],
3981 returns: Some(TypeRef::StringUtf8),
3982 doc: None,
3983 r#async: false,
3984 cancellable: false,
3985 deprecated: None,
3986 since: None,
3987 });
3988 m
3989 }]);
3990
3991 let config = NodeConfig {
3992 prefix: Some("myffi".into()),
3993 ..NodeConfig::default()
3994 };
3995
3996 let tmp = std::env::temp_dir().join("weaveffi_test_node_custom_prefix");
3997 let _ = std::fs::remove_dir_all(&tmp);
3998 std::fs::create_dir_all(&tmp).unwrap();
3999 let out_dir = Utf8Path::from_path(&tmp).expect("valid UTF-8");
4000
4001 NodeGenerator.generate(&api, out_dir, &config).unwrap();
4002
4003 let addon = std::fs::read_to_string(tmp.join("node/weaveffi_addon.c")).unwrap();
4006
4007 assert!(
4009 addon.contains("myffi_greet_hello"),
4010 "addon should call the prefixed user symbol myffi_greet_hello: {addon}"
4011 );
4012 assert!(
4013 !addon.contains("weaveffi_greet_hello"),
4014 "addon must not emit the hard-coded weaveffi_ user symbol: {addon}"
4015 );
4016 assert!(
4017 addon.contains("#include \"myffi.h\""),
4018 "addon should include the prefixed header myffi.h: {addon}"
4019 );
4020
4021 assert!(
4023 addon.contains("weaveffi_error"),
4024 "runtime weaveffi_error must remain literal: {addon}"
4025 );
4026 assert!(
4027 addon.contains("weaveffi_free_string"),
4028 "runtime weaveffi_free_string must remain literal: {addon}"
4029 );
4030
4031 let _ = std::fs::remove_dir_all(&tmp);
4032 }
4033
4034 #[test]
4035 fn node_addon_checks_error() {
4036 let api = make_api(vec![{
4037 let mut m = make_module("math");
4038 m.functions.push(Function {
4039 name: "add".into(),
4040 params: vec![
4041 Param {
4042 name: "a".into(),
4043 ty: TypeRef::I32,
4044 mutable: false,
4045 doc: None,
4046 },
4047 Param {
4048 name: "b".into(),
4049 ty: TypeRef::I32,
4050 mutable: false,
4051 doc: None,
4052 },
4053 ],
4054 returns: Some(TypeRef::I32),
4055 doc: None,
4056 r#async: false,
4057 cancellable: false,
4058 deprecated: None,
4059 since: None,
4060 });
4061 m
4062 }]);
4063 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
4064 assert!(
4065 addon.contains("err.code"),
4066 "generated addon.c should check err.code: {addon}"
4067 );
4068 }
4069
4070 #[test]
4071 fn node_strip_module_prefix() {
4072 let api = make_api(vec![{
4073 let mut m = make_module("contacts");
4074 m.functions.push(Function {
4075 name: "create_contact".into(),
4076 params: vec![Param {
4077 name: "name".into(),
4078 ty: TypeRef::StringUtf8,
4079 mutable: false,
4080 doc: None,
4081 }],
4082 returns: Some(TypeRef::I32),
4083 doc: None,
4084 r#async: false,
4085 cancellable: false,
4086 deprecated: None,
4087 since: None,
4088 });
4089 m
4090 }]);
4091
4092 let config = NodeConfig {
4093 strip_module_prefix: true,
4094 ..NodeConfig::default()
4095 };
4096
4097 let tmp = std::env::temp_dir().join("weaveffi_test_node_strip_prefix");
4098 let _ = std::fs::remove_dir_all(&tmp);
4099 std::fs::create_dir_all(&tmp).unwrap();
4100 let out_dir = Utf8Path::from_path(&tmp).expect("valid UTF-8");
4101
4102 NodeGenerator.generate(&api, out_dir, &config).unwrap();
4103
4104 let dts = std::fs::read_to_string(tmp.join("node/types.d.ts")).unwrap();
4105 assert!(
4106 dts.contains("export function create_contact("),
4107 "stripped name should be create_contact: {dts}"
4108 );
4109 assert!(
4110 !dts.contains("export function contacts_create_contact("),
4111 "should not contain module-prefixed name: {dts}"
4112 );
4113
4114 let addon = std::fs::read_to_string(tmp.join("node/weaveffi_addon.c")).unwrap();
4115 assert!(
4116 addon.contains("\"create_contact\""),
4117 "JS export name should be stripped: {addon}"
4118 );
4119 assert!(
4120 addon.contains("weaveffi_contacts_create_contact"),
4121 "C ABI call should still use full name: {addon}"
4122 );
4123
4124 let no_strip = NodeConfig::default();
4125 let tmp2 = std::env::temp_dir().join("weaveffi_test_node_no_strip_prefix");
4126 let _ = std::fs::remove_dir_all(&tmp2);
4127 std::fs::create_dir_all(&tmp2).unwrap();
4128 let out_dir2 = Utf8Path::from_path(&tmp2).expect("valid UTF-8");
4129
4130 NodeGenerator.generate(&api, out_dir2, &no_strip).unwrap();
4131
4132 let dts2 = std::fs::read_to_string(tmp2.join("node/types.d.ts")).unwrap();
4133 assert!(
4134 dts2.contains("export function contacts_create_contact("),
4135 "default should use module-prefixed name: {dts2}"
4136 );
4137
4138 let _ = std::fs::remove_dir_all(&tmp);
4139 let _ = std::fs::remove_dir_all(&tmp2);
4140 }
4141
4142 #[test]
4143 fn node_typed_handle_type() {
4144 let api = make_api(vec![{
4145 let mut m = make_module("contacts");
4146 m.structs.push(StructDef {
4147 name: "Contact".into(),
4148 doc: None,
4149 fields: vec![StructField {
4150 name: "name".into(),
4151 ty: TypeRef::StringUtf8,
4152 doc: None,
4153 default: None,
4154 }],
4155 builder: false,
4156 });
4157 m.functions.push(Function {
4158 name: "get_info".into(),
4159 params: vec![Param {
4160 name: "contact".into(),
4161 ty: TypeRef::TypedHandle("Contact".into()),
4162 mutable: false,
4163 doc: None,
4164 }],
4165 returns: None,
4166 doc: None,
4167 r#async: false,
4168 cancellable: false,
4169 deprecated: None,
4170 since: None,
4171 });
4172 m
4173 }]);
4174 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
4175 assert!(
4176 dts.contains("contact: Contact"),
4177 "TypedHandle should use class type not bigint: {dts}"
4178 );
4179 }
4180
4181 #[test]
4182 fn node_deeply_nested_optional() {
4183 let api = make_api(vec![Module {
4184 name: "edge".into(),
4185 functions: vec![Function {
4186 name: "process".into(),
4187 params: vec![Param {
4188 name: "data".into(),
4189 ty: TypeRef::Optional(Box::new(TypeRef::List(Box::new(TypeRef::Optional(
4190 Box::new(TypeRef::Struct("Contact".into())),
4191 ))))),
4192 mutable: false,
4193 doc: None,
4194 }],
4195 returns: None,
4196 doc: None,
4197 r#async: false,
4198 cancellable: false,
4199 deprecated: None,
4200 since: None,
4201 }],
4202 structs: vec![StructDef {
4203 name: "Contact".into(),
4204 doc: None,
4205 fields: vec![StructField {
4206 name: "name".into(),
4207 ty: TypeRef::StringUtf8,
4208 doc: None,
4209 default: None,
4210 }],
4211 builder: false,
4212 }],
4213 enums: vec![],
4214 callbacks: vec![],
4215 listeners: vec![],
4216 errors: None,
4217 modules: vec![],
4218 }]);
4219 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
4220 assert!(
4221 dts.contains("(Contact | null)[] | null"),
4222 "should contain deeply nested optional type: {dts}"
4223 );
4224 }
4225
4226 #[test]
4227 fn node_map_of_lists() {
4228 let api = make_api(vec![Module {
4229 name: "edge".into(),
4230 functions: vec![Function {
4231 name: "process".into(),
4232 params: vec![Param {
4233 name: "scores".into(),
4234 ty: TypeRef::Map(
4235 Box::new(TypeRef::StringUtf8),
4236 Box::new(TypeRef::List(Box::new(TypeRef::I32))),
4237 ),
4238 mutable: false,
4239 doc: None,
4240 }],
4241 returns: None,
4242 doc: None,
4243 r#async: false,
4244 cancellable: false,
4245 deprecated: None,
4246 since: None,
4247 }],
4248 structs: vec![],
4249 enums: vec![],
4250 callbacks: vec![],
4251 listeners: vec![],
4252 errors: None,
4253 modules: vec![],
4254 }]);
4255 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
4256 assert!(
4257 dts.contains("Record<string, number[]>"),
4258 "should contain map of lists type: {dts}"
4259 );
4260 }
4261
4262 #[test]
4263 fn node_enum_keyed_map() {
4264 let api = make_api(vec![Module {
4265 name: "edge".into(),
4266 functions: vec![Function {
4267 name: "process".into(),
4268 params: vec![Param {
4269 name: "contacts".into(),
4270 ty: TypeRef::Map(
4271 Box::new(TypeRef::Enum("Color".into())),
4272 Box::new(TypeRef::Struct("Contact".into())),
4273 ),
4274 mutable: false,
4275 doc: None,
4276 }],
4277 returns: None,
4278 doc: None,
4279 r#async: false,
4280 cancellable: false,
4281 deprecated: None,
4282 since: None,
4283 }],
4284 structs: vec![StructDef {
4285 name: "Contact".into(),
4286 doc: None,
4287 fields: vec![StructField {
4288 name: "name".into(),
4289 ty: TypeRef::StringUtf8,
4290 doc: None,
4291 default: None,
4292 }],
4293 builder: false,
4294 }],
4295 enums: vec![EnumDef {
4296 name: "Color".into(),
4297 doc: None,
4298 variants: vec![
4299 EnumVariant {
4300 name: "Red".into(),
4301 value: 0,
4302 doc: None,
4303 fields: vec![],
4304 },
4305 EnumVariant {
4306 name: "Green".into(),
4307 value: 1,
4308 doc: None,
4309 fields: vec![],
4310 },
4311 EnumVariant {
4312 name: "Blue".into(),
4313 value: 2,
4314 doc: None,
4315 fields: vec![],
4316 },
4317 ],
4318 }],
4319 callbacks: vec![],
4320 listeners: vec![],
4321 errors: None,
4322 modules: vec![],
4323 }]);
4324 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
4325 assert!(
4326 dts.contains("Record<Color, Contact>"),
4327 "should contain enum-keyed map type: {dts}"
4328 );
4329 }
4330
4331 #[test]
4332 fn node_no_double_free_on_error() {
4333 let api = make_api(vec![{
4334 let mut m = make_module("contacts");
4335 m.structs.push(StructDef {
4336 name: "Contact".into(),
4337 doc: None,
4338 fields: vec![StructField {
4339 name: "name".into(),
4340 ty: TypeRef::StringUtf8,
4341 doc: None,
4342 default: None,
4343 }],
4344 builder: false,
4345 });
4346 m.functions.push(Function {
4347 name: "find_contact".into(),
4348 params: vec![Param {
4349 name: "name".into(),
4350 ty: TypeRef::StringUtf8,
4351 mutable: false,
4352 doc: None,
4353 }],
4354 returns: Some(TypeRef::Struct("Contact".into())),
4355 doc: None,
4356 r#async: false,
4357 cancellable: false,
4358 deprecated: None,
4359 since: None,
4360 });
4361 m
4362 }]);
4363 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
4364 assert!(
4365 addon.contains("free(name)"),
4366 "malloc'd JS string copy should be freed after the C call: {addon}"
4367 );
4368 assert!(
4369 !addon.contains("weaveffi_free_string(name)"),
4370 "input string param must not use weaveffi_free_string: {addon}"
4371 );
4372 let free_pos = addon
4373 .find("free(name)")
4374 .expect("free(name) should be present");
4375 let err_pos = addon
4376 .find("if (err.code != 0)")
4377 .expect("err.code check should be present");
4378 assert!(
4379 free_pos < err_pos,
4380 "cleanup should run before error check: free at {free_pos}, err at {err_pos}"
4381 );
4382 let err_block_start = addon
4383 .find(" if (err.code != 0) {\n")
4384 .expect("error if block should be present");
4385 let after_err = &addon[err_block_start..];
4386 let err_block_end_rel = after_err
4387 .find(" }\n napi_value ret;")
4388 .expect("napi_value ret should follow error block");
4389 let err_block = &addon[err_block_start..err_block_start + err_block_end_rel];
4390 assert!(
4391 !err_block.contains("result"),
4392 "error path should not touch result before return NULL: {err_block}"
4393 );
4394 }
4395
4396 #[test]
4397 fn node_null_check_on_optional_return() {
4398 let api = make_api(vec![{
4399 let mut m = make_module("contacts");
4400 m.structs.push(StructDef {
4401 name: "Contact".into(),
4402 doc: None,
4403 fields: vec![StructField {
4404 name: "name".into(),
4405 ty: TypeRef::StringUtf8,
4406 doc: None,
4407 default: None,
4408 }],
4409 builder: false,
4410 });
4411 m.functions.push(Function {
4412 name: "find_contact".into(),
4413 params: vec![Param {
4414 name: "id".into(),
4415 ty: TypeRef::I32,
4416 mutable: false,
4417 doc: None,
4418 }],
4419 returns: Some(TypeRef::Optional(Box::new(TypeRef::Struct(
4420 "Contact".into(),
4421 )))),
4422 doc: None,
4423 r#async: false,
4424 cancellable: false,
4425 deprecated: None,
4426 since: None,
4427 });
4428 m
4429 }]);
4430 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
4431 assert!(
4432 addon.contains("if (result == NULL)"),
4433 "optional struct return should null-check before wrapping: {addon}"
4434 );
4435 assert!(
4436 addon.contains("napi_get_null"),
4437 "optional absent should return JS null via napi_get_null: {addon}"
4438 );
4439 }
4440
4441 #[test]
4442 fn node_async_returns_promise() {
4443 let api = make_api(vec![{
4444 let mut m = make_module("tasks");
4445 m.functions.push(Function {
4446 name: "run".into(),
4447 params: vec![Param {
4448 name: "id".into(),
4449 ty: TypeRef::I32,
4450 mutable: false,
4451 doc: None,
4452 }],
4453 returns: Some(TypeRef::StringUtf8),
4454 doc: None,
4455 r#async: true,
4456 cancellable: false,
4457 deprecated: None,
4458 since: None,
4459 });
4460 m.functions.push(Function {
4461 name: "fire_and_forget".into(),
4462 params: vec![],
4463 returns: None,
4464 doc: None,
4465 r#async: true,
4466 cancellable: false,
4467 deprecated: None,
4468 since: None,
4469 });
4470 m
4471 }]);
4472 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
4473 assert!(
4474 dts.contains("Promise<"),
4475 "async function should return Promise in .d.ts: {dts}"
4476 );
4477 assert!(
4478 dts.contains("): Promise<string>"),
4479 "async string return should be Promise<string>: {dts}"
4480 );
4481 assert!(
4482 dts.contains("): Promise<void>"),
4483 "async void return should be Promise<void>: {dts}"
4484 );
4485 }
4486
4487 #[test]
4488 fn node_addon_creates_promise() {
4489 let api = make_api(vec![{
4490 let mut m = make_module("tasks");
4491 m.functions.push(Function {
4492 name: "run".into(),
4493 params: vec![Param {
4494 name: "id".into(),
4495 ty: TypeRef::I32,
4496 mutable: false,
4497 doc: None,
4498 }],
4499 returns: Some(TypeRef::I32),
4500 doc: None,
4501 r#async: true,
4502 cancellable: false,
4503 deprecated: None,
4504 since: None,
4505 });
4506 m
4507 }]);
4508 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
4509 assert!(
4510 addon.contains("napi_create_promise"),
4511 "async addon should call napi_create_promise: {addon}"
4512 );
4513 assert!(
4514 addon.contains("napi_resolve_deferred"),
4515 "async callback should call napi_resolve_deferred: {addon}"
4516 );
4517 assert!(
4518 addon.contains("napi_reject_deferred"),
4519 "async callback should call napi_reject_deferred: {addon}"
4520 );
4521 assert!(
4522 addon.contains("weaveffi_tasks_run_napi_actx"),
4523 "async addon should define per-fn async context struct: {addon}"
4524 );
4525 assert!(
4526 addon.contains("weaveffi_tasks_run_async("),
4527 "async addon should call the _async C function: {addon}"
4528 );
4529 assert!(
4530 addon.contains("weaveffi_tasks_run_napi_cb"),
4531 "async addon should define the callback: {addon}"
4532 );
4533 assert!(
4536 addon.contains("napi_call_threadsafe_function(ctx->tsfn, ctx, napi_tsfn_blocking)"),
4537 "completion callback must hop to the JS thread via tsfn: {addon}"
4538 );
4539 assert!(
4540 !addon.contains("napi_resolve_deferred(ctx->env"),
4541 "deferred must never be settled from the producer thread: {addon}"
4542 );
4543 }
4544
4545 #[test]
4551 fn node_async_pins_callback_for_lifetime() {
4552 let api = make_api(vec![{
4553 let mut m = make_module("tasks");
4554 m.functions.push(Function {
4555 name: "run".into(),
4556 params: vec![Param {
4557 name: "id".into(),
4558 ty: TypeRef::I32,
4559 mutable: false,
4560 doc: None,
4561 }],
4562 returns: Some(TypeRef::I32),
4563 doc: None,
4564 r#async: true,
4565 cancellable: false,
4566 deprecated: None,
4567 since: None,
4568 });
4569 m
4570 }]);
4571 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
4572 let create_count = addon.matches("napi_create_promise").count();
4573 let resolve_count = addon.matches("napi_resolve_deferred").count();
4574 let reject_count = addon.matches("napi_reject_deferred").count();
4575 let alloc_count = addon
4576 .matches("calloc(1, sizeof(weaveffi_tasks_run_napi_actx))")
4577 .count();
4578 let free_count = addon.matches("free(ctx);").count();
4579 let release_count = addon
4580 .matches("napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_release);")
4581 .count();
4582 assert_eq!(
4583 create_count, 1,
4584 "expected one napi_create_promise per async fn, got {create_count}: {addon}"
4585 );
4586 assert_eq!(
4587 resolve_count, 1,
4588 "expected one napi_resolve_deferred per async fn, got {resolve_count}: {addon}"
4589 );
4590 assert_eq!(
4591 reject_count, 1,
4592 "expected one napi_reject_deferred per async fn, got {reject_count}: {addon}"
4593 );
4594 assert_eq!(
4595 alloc_count, free_count,
4596 "ctx alloc / free must balance per async fn: alloc={alloc_count} free={free_count}: {addon}"
4597 );
4598 assert_eq!(
4599 release_count, 1,
4600 "tsfn must be released exactly once per async fn, got {release_count}: {addon}"
4601 );
4602 }
4603
4604 fn doc_module() -> Module {
4605 Module {
4606 name: "docs".into(),
4607 functions: vec![Function {
4608 name: "do_thing".into(),
4609 params: vec![Param {
4610 name: "x".into(),
4611 ty: TypeRef::I32,
4612 mutable: false,
4613 doc: Some("the input value".into()),
4614 }],
4615 returns: Some(TypeRef::I32),
4616 doc: Some("Performs a thing.".into()),
4617 r#async: false,
4618 cancellable: false,
4619 deprecated: None,
4620 since: None,
4621 }],
4622 structs: vec![StructDef {
4623 name: "Item".into(),
4624 doc: Some("An item we track.".into()),
4625 fields: vec![StructField {
4626 name: "id".into(),
4627 ty: TypeRef::I64,
4628 doc: Some("Stable id".into()),
4629 default: None,
4630 }],
4631 builder: false,
4632 }],
4633 enums: vec![EnumDef {
4634 name: "Kind".into(),
4635 doc: Some("Kind of item.".into()),
4636 variants: vec![EnumVariant {
4637 name: "Small".into(),
4638 value: 0,
4639 doc: Some("A small one".into()),
4640 fields: vec![],
4641 }],
4642 }],
4643 callbacks: vec![],
4644 listeners: vec![],
4645 errors: None,
4646 modules: vec![],
4647 }
4648 }
4649
4650 #[test]
4651 fn node_emits_doc_on_function() {
4652 let dts = render_node_dts(
4653 &make_api(vec![doc_module()]),
4654 "weaveffi",
4655 true,
4656 "weaveffi.yml",
4657 );
4658 assert!(dts.contains("Performs a thing."), "{dts}");
4659 }
4660
4661 #[test]
4662 fn node_emits_doc_on_struct() {
4663 let dts = render_node_dts(
4664 &make_api(vec![doc_module()]),
4665 "weaveffi",
4666 true,
4667 "weaveffi.yml",
4668 );
4669 assert!(dts.contains("/** An item we track. */"), "{dts}");
4670 }
4671
4672 #[test]
4673 fn node_emits_doc_on_enum_variant() {
4674 let dts = render_node_dts(
4675 &make_api(vec![doc_module()]),
4676 "weaveffi",
4677 true,
4678 "weaveffi.yml",
4679 );
4680 assert!(dts.contains("/** Kind of item. */"), "{dts}");
4681 assert!(dts.contains("/** A small one */"), "{dts}");
4682 }
4683
4684 #[test]
4685 fn node_emits_doc_on_field() {
4686 let dts = render_node_dts(
4687 &make_api(vec![doc_module()]),
4688 "weaveffi",
4689 true,
4690 "weaveffi.yml",
4691 );
4692 assert!(dts.contains("/** Stable id */"), "{dts}");
4693 }
4694
4695 #[test]
4696 fn node_emits_doc_on_param() {
4697 let dts = render_node_dts(
4698 &make_api(vec![doc_module()]),
4699 "weaveffi",
4700 true,
4701 "weaveffi.yml",
4702 );
4703 assert!(dts.contains("@param x the input value"), "{dts}");
4704 }
4705
4706 fn shapes_module() -> Module {
4712 fn field(name: &str, ty: TypeRef) -> StructField {
4713 StructField {
4714 name: name.into(),
4715 ty,
4716 doc: None,
4717 default: None,
4718 }
4719 }
4720 fn variant(name: &str, value: i32, fields: Vec<StructField>) -> EnumVariant {
4721 EnumVariant {
4722 name: name.into(),
4723 value,
4724 doc: None,
4725 fields,
4726 }
4727 }
4728 Module {
4729 name: "shapes".into(),
4730 functions: vec![
4731 Function {
4732 name: "describe".into(),
4733 params: vec![Param {
4734 name: "shape".into(),
4735 ty: TypeRef::Struct("Shape".into()),
4736 mutable: false,
4737 doc: None,
4738 }],
4739 returns: Some(TypeRef::StringUtf8),
4740 doc: None,
4741 r#async: false,
4742 cancellable: false,
4743 deprecated: None,
4744 since: None,
4745 },
4746 Function {
4747 name: "scale".into(),
4748 params: vec![
4749 Param {
4750 name: "shape".into(),
4751 ty: TypeRef::Struct("Shape".into()),
4752 mutable: false,
4753 doc: None,
4754 },
4755 Param {
4756 name: "factor".into(),
4757 ty: TypeRef::F64,
4758 mutable: false,
4759 doc: None,
4760 },
4761 ],
4762 returns: Some(TypeRef::Struct("Shape".into())),
4763 doc: None,
4764 r#async: false,
4765 cancellable: false,
4766 deprecated: None,
4767 since: None,
4768 },
4769 Function {
4770 name: "sum_bytes".into(),
4771 params: vec![Param {
4772 name: "values".into(),
4773 ty: TypeRef::List(Box::new(TypeRef::U8)),
4774 mutable: false,
4775 doc: None,
4776 }],
4777 returns: Some(TypeRef::U64),
4778 doc: None,
4779 r#async: false,
4780 cancellable: false,
4781 deprecated: None,
4782 since: None,
4783 },
4784 ],
4785 structs: vec![],
4786 enums: vec![
4787 EnumDef {
4788 name: "Shape".into(),
4789 doc: None,
4790 variants: vec![
4791 variant("Empty", 0, vec![]),
4792 variant("Circle", 1, vec![field("radius", TypeRef::F64)]),
4793 variant(
4794 "Rectangle",
4795 2,
4796 vec![field("width", TypeRef::F32), field("height", TypeRef::F32)],
4797 ),
4798 variant(
4799 "Labeled",
4800 3,
4801 vec![
4802 field("label", TypeRef::StringUtf8),
4803 field("count", TypeRef::U8),
4804 ],
4805 ),
4806 ],
4807 },
4808 EnumDef {
4809 name: "Channel".into(),
4810 doc: None,
4811 variants: vec![
4812 EnumVariant {
4813 name: "Red".into(),
4814 value: 0,
4815 doc: None,
4816 fields: vec![],
4817 },
4818 EnumVariant {
4819 name: "Green".into(),
4820 value: 1,
4821 doc: None,
4822 fields: vec![],
4823 },
4824 ],
4825 },
4826 ],
4827 callbacks: vec![],
4828 listeners: vec![],
4829 errors: None,
4830 modules: vec![],
4831 }
4832 }
4833
4834 #[test]
4835 fn rich_enum_addon_exposes_native_helpers() {
4836 let addon = render_addon_c(
4837 &make_api(vec![shapes_module()]),
4838 "weaveffi",
4839 false,
4840 "shapes.yml",
4841 );
4842
4843 for sym in [
4846 "Napi_weaveffi_shapes_Shape_tag",
4847 "Napi_weaveffi_shapes_Shape_Empty_new",
4848 "Napi_weaveffi_shapes_Shape_Circle_new",
4849 "Napi_weaveffi_shapes_Shape_Rectangle_new",
4850 "Napi_weaveffi_shapes_Shape_Labeled_new",
4851 "Napi_weaveffi_shapes_Shape_Circle_get_radius",
4852 "Napi_weaveffi_shapes_Shape_Rectangle_get_width",
4853 "Napi_weaveffi_shapes_Shape_Rectangle_get_height",
4854 "Napi_weaveffi_shapes_Shape_Labeled_get_label",
4855 "Napi_weaveffi_shapes_Shape_Labeled_get_count",
4856 "Napi_weaveffi_shapes_Shape_destroy",
4857 ] {
4858 assert!(addon.contains(sym), "missing native helper {sym}: {addon}");
4859 }
4860
4861 for js in [
4863 "\"shapes_Shape_tag\"",
4864 "\"shapes_Shape_empty_new\"",
4865 "\"shapes_Shape_circle_new\"",
4866 "\"shapes_Shape_rectangle_new\"",
4867 "\"shapes_Shape_labeled_new\"",
4868 "\"shapes_Shape_circle_get_radius\"",
4869 "\"shapes_Shape_labeled_get_label\"",
4870 "\"shapes_Shape_labeled_get_count\"",
4871 "\"shapes_Shape_destroy\"",
4872 ] {
4873 assert!(addon.contains(js), "missing JS export {js}: {addon}");
4874 }
4875 }
4876
4877 #[test]
4878 fn rich_enum_addon_calls_c_abi_correctly() {
4879 let addon = render_addon_c(
4880 &make_api(vec![shapes_module()]),
4881 "weaveffi",
4882 false,
4883 "shapes.yml",
4884 );
4885
4886 assert!(
4888 addon.contains(
4889 "weaveffi_shapes_Shape* result = weaveffi_shapes_Shape_Circle_new(radius, &err);"
4890 ),
4891 "circle ctor must call the C constructor: {addon}"
4892 );
4893 assert!(
4895 addon.contains(
4896 "weaveffi_shapes_Shape_Rectangle_new((float)width_raw, (float)height_raw, &err);"
4897 ),
4898 "rectangle ctor must narrow f32 args: {addon}"
4899 );
4900 assert!(
4902 addon.contains("weaveffi_shapes_Shape_Labeled_new(label, (uint8_t)count_raw, &err);"),
4903 "labeled ctor must marshal string + u8: {addon}"
4904 );
4905 assert!(
4906 addon.contains("free(label);"),
4907 "labeled ctor must free its string copy: {addon}"
4908 );
4909 assert!(
4911 addon.contains("napi_create_int32(env, weaveffi_shapes_Shape_tag(self), &ret);"),
4912 "tag reader must return the discriminant: {addon}"
4913 );
4914 assert!(
4916 addon.contains("weaveffi_free_string(ret_s);"),
4917 "string field getter must free the owned C string: {addon}"
4918 );
4919 assert!(
4921 addon.contains("weaveffi_shapes_Shape_destroy(self);"),
4922 "destructor must free the object: {addon}"
4923 );
4924
4925 assert!(
4928 addon.contains(
4929 "weaveffi_shapes_describe((const weaveffi_shapes_Shape*)(intptr_t)shape_raw, &err);"
4930 ),
4931 "describe must pass the opaque handle: {addon}"
4932 );
4933 assert!(
4934 addon.contains(
4935 "weaveffi_shapes_Shape* result = weaveffi_shapes_scale((const weaveffi_shapes_Shape*)(intptr_t)shape_raw, factor, &err);"
4936 ),
4937 "scale must take and return the opaque handle: {addon}"
4938 );
4939 assert!(
4940 addon.contains("napi_create_int64(env, (int64_t)(intptr_t)result, &ret);"),
4941 "scale must return the opaque handle as int64: {addon}"
4942 );
4943 }
4944
4945 #[test]
4946 fn rich_enum_index_js_exposes_class() {
4947 let index = render_node_index(
4948 &make_api(vec![shapes_module()]),
4949 "weaveffi",
4950 false,
4951 "shapes.yml",
4952 );
4953
4954 assert!(
4955 index.contains("class Shape {"),
4956 "missing Shape class: {index}"
4957 );
4958 for factory in [
4960 "static empty() {",
4961 "static circle(radius) {",
4962 "static rectangle(width, height) {",
4963 "static labeled(label, count) {",
4964 ] {
4965 assert!(
4966 index.contains(factory),
4967 "missing factory `{factory}`: {index}"
4968 );
4969 }
4970 assert!(
4972 index.contains("return new Shape(addon.shapes_Shape_circle_new(radius));"),
4973 "circle factory must call the native ctor: {index}"
4974 );
4975 assert!(
4977 index.contains("tag() {") && index.contains("addon.shapes_Shape_tag(this._handle)"),
4978 "missing tag(): {index}"
4979 );
4980 for getter in [
4981 "get circleRadius() {",
4982 "get rectangleWidth() {",
4983 "get rectangleHeight() {",
4984 "get labeledLabel() {",
4985 "get labeledCount() {",
4986 ] {
4987 assert!(index.contains(getter), "missing getter `{getter}`: {index}");
4988 }
4989 assert!(
4991 index.contains("destroy() {")
4992 && index.contains("addon.shapes_Shape_destroy(this._handle)"),
4993 "missing destroy(): {index}"
4994 );
4995 assert!(
4996 index.contains("new FinalizationRegistry"),
4997 "missing FinalizationRegistry cleanup: {index}"
4998 );
4999 assert!(
5001 index.contains(
5002 "Shape.Tag = Object.freeze({ Empty: 0, Circle: 1, Rectangle: 2, Labeled: 3 });"
5003 ),
5004 "missing Tag discriminant map: {index}"
5005 );
5006 assert!(
5009 index.contains("wv.shapes_scale = function (shape, factor) {")
5010 && index.contains("return new Shape(addon.shapes_scale(shape instanceof Shape ? shape._handle : shape, factor));"),
5011 "scale must be rewrapped to return a Shape: {index}"
5012 );
5013 assert!(
5014 index.contains(
5015 "return addon.shapes_describe(shape instanceof Shape ? shape._handle : shape);"
5016 ),
5017 "describe must unwrap a Shape argument: {index}"
5018 );
5019 assert!(
5021 !index.contains("wv.shapes_sum_bytes = function"),
5022 "sum_bytes must not be rewrapped: {index}"
5023 );
5024 }
5025
5026 #[test]
5027 fn rich_enum_index_js_without_rich_is_plain_reexport() {
5028 let mut m = make_module("math");
5030 m.functions.push(Function {
5031 name: "add".into(),
5032 params: vec![Param {
5033 name: "a".into(),
5034 ty: TypeRef::I32,
5035 mutable: false,
5036 doc: None,
5037 }],
5038 returns: Some(TypeRef::I32),
5039 doc: None,
5040 r#async: false,
5041 cancellable: false,
5042 deprecated: None,
5043 since: None,
5044 });
5045 let index = render_node_index(&make_api(vec![m]), "weaveffi", false, "weaveffi.yml");
5046 assert!(
5047 index.contains("module.exports = addon;"),
5048 "no-rich-enum index must re-export the addon directly: {index}"
5049 );
5050 assert!(
5051 !index.contains("class "),
5052 "no class should be emitted: {index}"
5053 );
5054 }
5055
5056 #[test]
5057 fn rich_enum_dts_emits_class_not_enum() {
5058 let dts = render_node_dts(
5059 &make_api(vec![shapes_module()]),
5060 "weaveffi",
5061 false,
5062 "shapes.yml",
5063 );
5064
5065 assert!(
5067 dts.contains("export class Shape {"),
5068 "rich enum must be a class: {dts}"
5069 );
5070 assert!(
5071 !dts.contains("export enum Shape"),
5072 "rich enum must not be a plain enum: {dts}"
5073 );
5074 assert!(
5075 dts.contains("static circle(radius: number): Shape;"),
5076 "{dts}"
5077 );
5078 assert!(
5079 dts.contains("static labeled(label: string, count: number): Shape;"),
5080 "{dts}"
5081 );
5082 assert!(dts.contains("tag(): number;"), "{dts}");
5083 assert!(dts.contains("get circleRadius(): number;"), "{dts}");
5084 assert!(dts.contains("get labeledLabel(): string;"), "{dts}");
5085 assert!(dts.contains("destroy(): void;"), "{dts}");
5086
5087 assert!(
5089 dts.contains("export enum Channel {"),
5090 "plain enum stays an enum: {dts}"
5091 );
5092
5093 assert!(
5095 dts.contains("export function shapes_describe(shape: Shape): string"),
5096 "{dts}"
5097 );
5098 assert!(
5099 dts.contains("export function shapes_scale(shape: Shape, factor: number): Shape"),
5100 "{dts}"
5101 );
5102 }
5103}