1use alef_codegen::generators::trait_bridge::{TraitBridgeGenerator, TraitBridgeSpec, gen_bridge_all};
7use alef_core::config::TraitBridgeConfig;
8use alef_core::ir::{ApiSurface, MethodDef, TypeDef, TypeRef};
9use std::collections::HashMap;
10use std::fmt::Write;
11
12pub struct NapiBridgeGenerator {
15 pub core_import: String,
17 pub type_paths: HashMap<String, String>,
19}
20
21impl TraitBridgeGenerator for NapiBridgeGenerator {
22 fn foreign_object_type(&self) -> &str {
23 "napi::bindgen_prelude::Object<'static>"
24 }
25
26 fn bridge_imports(&self) -> Vec<String> {
27 vec![
28 "use napi::bindgen_prelude::{JsObjectValue, ToNapiValue, Unknown, Object};".to_string(),
29 "use napi::JsValue;".to_string(),
30 "use std::sync::Arc;".to_string(),
31 ]
32 }
33
34 fn gen_sync_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
35 let name = &method.name;
36 let has_error = method.error_type.is_some();
37 let mut out = String::with_capacity(512);
38
39 let js_args_exprs = build_napi_args(method);
41 let args_tuple_ty = unknown_tuple_type(js_args_exprs.len());
42
43 writeln!(
44 out,
45 "let func: napi::bindgen_prelude::Function<{args_tuple_ty}, napi::bindgen_prelude::Unknown> = match self.inner.get_named_property(\"{name}\") {{"
46 )
47 .ok();
48 writeln!(out, " Ok(f) => f,").ok();
49 if has_error {
50 writeln!(out, " Err(e) => return Err({}::Error::new(", spec.core_import).ok();
51 writeln!(
52 out,
53 " format!(\"Method '{{}}' not found on bridge object: {{}}\", self.cached_name, e)"
54 )
55 .ok();
56 writeln!(out, " )),").ok();
57 } else {
58 writeln!(out, " Err(_) => return Default::default(),").ok();
59 }
60 writeln!(out, "}};").ok();
61
62 if js_args_exprs.is_empty() {
64 writeln!(out, "let result = func.call(());").ok();
65 } else {
66 for (i, expr) in js_args_exprs.iter().enumerate() {
68 writeln!(out, "let arg_{i}: napi::bindgen_prelude::Unknown = {expr};").ok();
69 }
70 let tuple_args: Vec<String> = (0..js_args_exprs.len()).map(|i| format!("arg_{i}")).collect();
71 let tuple_str = if js_args_exprs.len() == 1 {
72 format!("({},)", tuple_args[0])
73 } else {
74 format!("({})", tuple_args.join(", "))
75 };
76 writeln!(out, "let result = func.call({tuple_str});").ok();
77 }
78
79 writeln!(out, "match result {{").ok();
81 if has_error {
82 writeln!(out, " Err(e) => Err({}::Error::new(", spec.core_import).ok();
83 writeln!(
84 out,
85 " format!(\"Plugin '{{}}' method '{name}' failed: {{}}\", self.cached_name, e)"
86 )
87 .ok();
88 writeln!(out, " )),").ok();
89 } else {
90 writeln!(out, " Err(_) => Ok(Default::default()),").ok();
91 }
92 writeln!(out, " Ok(val) => {{").ok();
93 if matches!(method.return_type, TypeRef::Unit) {
94 writeln!(out, " Ok(())").ok();
95 } else {
96 writeln!(out, " // Convert JS value to Rust type").ok();
97 writeln!(out, " extract_napi_value(&val).map_err(|e| {{").ok();
98 writeln!(out, " {}::Error::new(", spec.core_import).ok();
99 writeln!(
100 out,
101 " format!(\"Failed to extract return value from method '{name}': {{}}\", e)"
102 )
103 .ok();
104 writeln!(out, " )").ok();
105 writeln!(out, " }})").ok();
106 }
107 writeln!(out, " }}").ok();
108 writeln!(out, "}}").ok();
109 out
110 }
111
112 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
113 let name = &method.name;
114 let mut out = String::with_capacity(1024);
115
116 writeln!(out, "let cached_name = self.cached_name.clone();").ok();
118
119 let js_args_exprs = build_napi_args(method);
121 let args_tuple_ty = unknown_tuple_type(js_args_exprs.len());
122
123 writeln!(
124 out,
125 "let func: napi::bindgen_prelude::Function<{args_tuple_ty}, napi::bindgen_prelude::Unknown> = match self.inner.get_named_property(\"{name}\") {{"
126 )
127 .ok();
128 writeln!(out, " Ok(f) => f,").ok();
129 writeln!(out, " Err(e) => {{").ok();
130 writeln!(out, " return Box::pin(async move {{").ok();
131 writeln!(out, " Err({}::Error::new(", spec.core_import).ok();
132 writeln!(
133 out,
134 " format!(\"Method '{{}}' not found on bridge object: {{}}\", cached_name, e)"
135 )
136 .ok();
137 writeln!(out, " ))").ok();
138 writeln!(out, " }});").ok();
139 writeln!(out, " }}").ok();
140 writeln!(out, "}};").ok();
141
142 for (i, expr) in js_args_exprs.iter().enumerate() {
144 writeln!(out, "let arg_{i}: napi::bindgen_prelude::Unknown = {expr};").ok();
145 }
146
147 writeln!(out, "Box::pin(async move {{").ok();
148 let tuple_str = if js_args_exprs.is_empty() {
149 "()".to_string()
150 } else {
151 let tuple_args: Vec<String> = (0..js_args_exprs.len()).map(|i| format!("arg_{i}")).collect();
152 if js_args_exprs.len() == 1 {
153 format!("({},)", tuple_args[0])
154 } else {
155 format!("({})", tuple_args.join(", "))
156 }
157 };
158
159 writeln!(out, " let result = func.call({tuple_str});").ok();
160 writeln!(out, " match result {{").ok();
161 writeln!(out, " Err(e) => Err({}::Error::new(", spec.core_import).ok();
162 writeln!(
163 out,
164 " format!(\"Plugin '{{}}' method '{name}' failed: {{}}\", cached_name, e)"
165 )
166 .ok();
167 writeln!(out, " )),").ok();
168 writeln!(out, " Ok(val) => {{").ok();
169 if matches!(method.return_type, TypeRef::Unit) {
170 writeln!(out, " Ok(())").ok();
171 } else {
172 writeln!(out, " extract_napi_value(&val).map_err(|e| {{").ok();
173 writeln!(out, " {}::Error::new(", spec.core_import).ok();
174 writeln!(
175 out,
176 " format!(\"Failed to extract return value from method '{name}': {{}}\", e)"
177 )
178 .ok();
179 writeln!(out, " )").ok();
180 writeln!(out, " }})").ok();
181 }
182 writeln!(out, " }}").ok();
183 writeln!(out, " }}").ok();
184 writeln!(out, "}})").ok();
185 out
186 }
187
188 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
189 let wrapper = spec.wrapper_name();
190 let mut out = String::with_capacity(512);
191
192 writeln!(out, "impl {wrapper} {{").ok();
193 writeln!(out, " /// Create a new bridge wrapping a NAPI Object.").ok();
194 writeln!(out, " ///").ok();
195 writeln!(out, " /// Validates that the object provides all required methods.").ok();
196 writeln!(
197 out,
198 " pub fn new(js_obj: napi::bindgen_prelude::Object<'_>) -> napi::Result<Self> {{"
199 )
200 .ok();
201
202 for req_method in spec.required_methods() {
204 writeln!(
205 out,
206 " if !js_obj.has_named_property(\"{}\").unwrap_or(false) {{",
207 req_method.name
208 )
209 .ok();
210 writeln!(out, " return Err(napi::Error::new(").ok();
211 writeln!(out, " napi::Status::GenericFailure,").ok();
212 writeln!(
213 out,
214 " format!(\"Object missing required method: {{}}\", \"{}\")",
215 req_method.name
216 )
217 .ok();
218 writeln!(out, " ));").ok();
219 writeln!(out, " }}").ok();
220 }
221
222 writeln!(
224 out,
225 " // SAFETY: The JS object is owned by the Node.js runtime and lives for"
226 )
227 .ok();
228 writeln!(
229 out,
230 " // the duration of the enclosing #[napi] call. The bridge is only used"
231 )
232 .ok();
233 writeln!(
234 out,
235 " // synchronously during that same call, so 'static is safe here."
236 )
237 .ok();
238 writeln!(
239 out,
240 " let js_obj: napi::bindgen_prelude::Object<'static> = unsafe {{"
241 )
242 .ok();
243 writeln!(out, " std::mem::transmute(js_obj)").ok();
244 writeln!(out, " }};").ok();
245
246 writeln!(
248 out,
249 " let cached_name = match js_obj.get_named_property::<String>(\"name\") {{"
250 )
251 .ok();
252 writeln!(out, " Ok(n) => n,").ok();
253 writeln!(out, " Err(_) => \"unknown\".to_string(),").ok();
254 writeln!(out, " }};").ok();
255
256 writeln!(out, " Ok(Self {{").ok();
257 writeln!(out, " inner: js_obj,").ok();
258 writeln!(out, " cached_name,").ok();
259 writeln!(out, " }})").ok();
260 writeln!(out, " }}").ok();
261 writeln!(out, "}}").ok();
262 out
263 }
264
265 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
266 let Some(register_fn) = spec.bridge_config.register_fn.as_deref() else {
267 return String::new();
268 };
269 let Some(registry_getter) = spec.bridge_config.registry_getter.as_deref() else {
270 return String::new();
271 };
272 let wrapper = spec.wrapper_name();
273 let trait_path = spec.trait_path();
274
275 let mut out = String::with_capacity(1024);
276
277 writeln!(out, "#[napi]").ok();
278 writeln!(
279 out,
280 "pub fn {register_fn}(obj: napi::bindgen_prelude::Object) -> napi::Result<()> {{"
281 )
282 .ok();
283
284 writeln!(out, " let bridge = {wrapper}::new(obj)?;").ok();
286 writeln!(out, " let arc: Arc<dyn {trait_path}> = Arc::new(bridge);").ok();
287
288 writeln!(out, " let registry = {registry_getter}();").ok();
290 writeln!(out, " let mut registry = registry.write();").ok();
291 writeln!(out, " registry.register(arc).map_err(|e| napi::Error::new(").ok();
292 writeln!(out, " napi::Status::GenericFailure,").ok();
293 writeln!(out, " format!(\"Failed to register backend: {{}}\", e)").ok();
294 writeln!(out, " ))").ok();
295 writeln!(out, "}}").ok();
296 out
297 }
298}
299
300pub fn gen_trait_bridge(
302 trait_type: &TypeDef,
303 bridge_cfg: &TraitBridgeConfig,
304 core_import: &str,
305 api: &ApiSurface,
306) -> String {
307 let type_paths: HashMap<String, String> = api
309 .types
310 .iter()
311 .map(|t| (t.name.clone(), t.rust_path.replace('-', "_")))
312 .chain(
313 api.enums
314 .iter()
315 .map(|e| (e.name.clone(), e.rust_path.replace('-', "_"))),
316 )
317 .collect();
318
319 let is_visitor_bridge = bridge_cfg.type_alias.is_some()
321 && bridge_cfg.register_fn.is_none()
322 && bridge_cfg.super_trait.is_none()
323 && trait_type.methods.iter().all(|m| m.has_default_impl);
324
325 if is_visitor_bridge {
326 let struct_name = format!("Js{}Bridge", bridge_cfg.trait_name);
327 let trait_path = trait_type.rust_path.replace('-', "_");
328 gen_visitor_bridge(
329 trait_type,
330 bridge_cfg,
331 &struct_name,
332 &trait_path,
333 core_import,
334 &type_paths,
335 )
336 } else {
337 let generator = NapiBridgeGenerator {
339 core_import: core_import.to_string(),
340 type_paths: type_paths.clone(),
341 };
342 let spec = TraitBridgeSpec {
343 trait_def: trait_type,
344 bridge_config: bridge_cfg,
345 core_import,
346 wrapper_prefix: "Js",
347 type_paths,
348 };
349 gen_bridge_all(&spec, &generator)
350 }
351}
352
353fn gen_visitor_bridge(
358 trait_type: &TypeDef,
359 _bridge_cfg: &TraitBridgeConfig,
360 struct_name: &str,
361 trait_path: &str,
362 core_crate: &str,
363 type_paths: &HashMap<String, String>,
364) -> String {
365 let mut out = String::with_capacity(8192);
366 writeln!(out, "#[allow(unused_imports)]").unwrap();
369 writeln!(
370 out,
371 "use napi::bindgen_prelude::{{JsObjectValue, ToNapiValue, Unknown, Object}};"
372 )
373 .unwrap();
374 writeln!(out, "#[allow(unused_imports)]").unwrap();
375 writeln!(out, "use napi::JsValue;").unwrap();
376 writeln!(out).unwrap();
377
378 writeln!(out, "fn nodecontext_to_js_object<'e>(").unwrap();
380 writeln!(out, " env: &'e napi::Env,").unwrap();
381 writeln!(out, " ctx: &{core_crate}::visitor::NodeContext,").unwrap();
382 writeln!(out, ") -> napi::Result<napi::bindgen_prelude::Object<'e>> {{").unwrap();
383 writeln!(out, " let mut obj = napi::bindgen_prelude::Object::new(env)?;").unwrap();
384 writeln!(
385 out,
386 " obj.set_named_property(\"nodeType\", env.create_string(&format!(\"{{:?}}\", ctx.node_type))?)?;"
387 )
388 .unwrap();
389 writeln!(
390 out,
391 " obj.set_named_property(\"tagName\", env.create_string(&ctx.tag_name)?)?;"
392 )
393 .unwrap();
394 writeln!(
395 out,
396 " obj.set_named_property(\"depth\", env.create_uint32(ctx.depth as u32)?)?;"
397 )
398 .unwrap();
399 writeln!(
400 out,
401 " obj.set_named_property(\"indexInParent\", env.create_uint32(ctx.index_in_parent as u32)?)?;"
402 )
403 .unwrap();
404 writeln!(out, " obj.set_named_property(\"isInline\", ctx.is_inline)?;").unwrap();
405 writeln!(out, " let parent_tag = match &ctx.parent_tag {{").unwrap();
406 writeln!(out, " Some(s) => env.create_string(s)?.to_unknown(),").unwrap();
407 writeln!(out, " None => {{").unwrap();
408 writeln!(
409 out,
410 " // SAFETY: napi_get_null returns a valid napi_value for the given env."
411 )
412 .unwrap();
413 writeln!(
414 out,
415 " let raw = unsafe {{ napi::bindgen_prelude::ToNapiValue::to_napi_value(env.raw(), napi::bindgen_prelude::Null)? }};"
416 )
417 .unwrap();
418 writeln!(
419 out,
420 " unsafe {{ napi::bindgen_prelude::Unknown::from_raw_unchecked(env.raw(), raw) }}"
421 )
422 .unwrap();
423 writeln!(out, " }}").unwrap();
424 writeln!(out, " }};").unwrap();
425 writeln!(out, " obj.set_named_property(\"parentTag\", parent_tag)?;").unwrap();
426 writeln!(out, " let mut attrs = napi::bindgen_prelude::Object::new(env)?;").unwrap();
427 writeln!(out, " for (k, v) in &ctx.attributes {{").unwrap();
428 writeln!(out, " attrs.set_named_property(k, env.create_string(v)?)?;").unwrap();
429 writeln!(out, " }}").unwrap();
430 writeln!(out, " obj.set_named_property(\"attributes\", attrs)?;").unwrap();
431 writeln!(out, " Ok(obj)").unwrap();
432 writeln!(out, "}}").unwrap();
433 writeln!(out).unwrap();
434
435 writeln!(out, "pub struct {struct_name} {{").unwrap();
439 writeln!(out, " obj: napi::bindgen_prelude::Object<'static>,").unwrap();
440 writeln!(out, "}}").unwrap();
441 writeln!(out).unwrap();
442
443 writeln!(out, "impl std::fmt::Debug for {struct_name} {{").unwrap();
445 writeln!(
446 out,
447 " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
448 )
449 .unwrap();
450 writeln!(out, " write!(f, \"{struct_name}\")").unwrap();
451 writeln!(out, " }}").unwrap();
452 writeln!(out, "}}").unwrap();
453 writeln!(out).unwrap();
454
455 writeln!(out, "impl {struct_name} {{").unwrap();
457 writeln!(
458 out,
459 " pub fn new(js_obj: napi::bindgen_prelude::Object<'_>) -> Self {{"
460 )
461 .unwrap();
462 writeln!(
463 out,
464 " // SAFETY: The JS object is owned by the Node.js runtime and lives for"
465 )
466 .unwrap();
467 writeln!(
468 out,
469 " // the duration of the enclosing #[napi] call. The bridge is only used"
470 )
471 .unwrap();
472 writeln!(
473 out,
474 " // synchronously during that same call, so 'static is safe here."
475 )
476 .unwrap();
477 writeln!(
478 out,
479 " let obj: napi::bindgen_prelude::Object<'static> = unsafe {{ std::mem::transmute(js_obj) }};"
480 )
481 .unwrap();
482 writeln!(out, " Self {{ obj }}").unwrap();
483 writeln!(out, " }}").unwrap();
484 writeln!(out).unwrap();
485
486 writeln!(out, " fn env(&self) -> napi::Env {{").unwrap();
489 writeln!(
490 out,
491 " // SAFETY: Object<'static> is 3 pointer-sized words; first word is napi_env."
492 )
493 .unwrap();
494 writeln!(
495 out,
496 " let raw: [*mut std::ffi::c_void; 3] = unsafe {{ std::mem::transmute_copy(&self.obj) }};"
497 )
498 .unwrap();
499 writeln!(out, " napi::Env::from_raw(raw[0] as napi::sys::napi_env)").unwrap();
500 writeln!(out, " }}").unwrap();
501 writeln!(out, "}}").unwrap();
502 writeln!(out).unwrap();
503
504 writeln!(out, "impl {trait_path} for {struct_name} {{").unwrap();
506 for method in &trait_type.methods {
507 if method.trait_source.is_some() {
508 continue;
509 }
510 gen_visitor_method_napi(&mut out, method, trait_path, core_crate, type_paths);
511 }
512 writeln!(out, "}}").unwrap();
513 writeln!(out).unwrap();
514 out
515}
516
517fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
519 if optional && matches!(ty, TypeRef::String) && is_ref {
520 return "Option<&str>".to_string();
521 }
522 if is_ref {
523 if let TypeRef::Vec(inner) = ty {
524 let inner_str = param_type(inner, "", false, tp);
525 return format!("&[{inner_str}]");
526 }
527 }
528 param_type(ty, "", is_ref, tp)
529}
530
531fn unknown_tuple_type(count: usize) -> String {
533 if count == 0 {
534 return "()".to_string();
535 }
536 let parts = vec!["napi::bindgen_prelude::Unknown"; count];
537 format!("({}{})", parts.join(", "), if count == 1 { "," } else { "" })
538}
539
540fn gen_visitor_method_napi(
542 out: &mut String,
543 method: &MethodDef,
544 _trait_path: &str,
545 _core_crate: &str,
546 type_paths: &HashMap<String, String>,
547) {
548 let name = &method.name;
549
550 let js_name = to_camel_case(name);
552
553 let mut sig_parts = vec!["&mut self".to_string()];
554 for p in &method.params {
555 let ty_str = visitor_param_type(&p.ty, p.is_ref, p.optional, type_paths);
556 sig_parts.push(format!("{}: {}", p.name, ty_str));
557 }
558 let sig = sig_parts.join(", ");
559
560 let ret_ty = match &method.return_type {
561 TypeRef::Named(n) => type_paths
562 .get(n.as_str())
563 .map(|p| p.replace('-', "_"))
564 .unwrap_or_else(|| n.clone()),
565 other => param_type(other, "", false, type_paths),
566 };
567
568 writeln!(out, " fn {name}({sig}) -> {ret_ty} {{").unwrap();
569
570 writeln!(
572 out,
573 " let has_method = self.obj.has_named_property(\"{js_name}\").unwrap_or(false);"
574 )
575 .unwrap();
576 writeln!(out, " if !has_method {{").unwrap();
577 writeln!(out, " return {ret_ty}::Continue;").unwrap();
578 writeln!(out, " }}").unwrap();
579
580 let arg_count = method.params.len();
582 let args_tuple_ty = unknown_tuple_type(arg_count);
583 writeln!(
584 out,
585 " let func: napi::bindgen_prelude::Function<{args_tuple_ty}, napi::bindgen_prelude::Unknown> = match self.obj.get_named_property(\"{js_name}\") {{"
586 )
587 .unwrap();
588 writeln!(out, " Ok(f) => f,").unwrap();
589 writeln!(out, " Err(_) => return {ret_ty}::Continue,").unwrap();
590 writeln!(out, " }};").unwrap();
591
592 let js_args_exprs = build_napi_args(method);
594 if arg_count == 0 {
595 writeln!(out, " let result = func.call(());").unwrap();
596 } else {
597 writeln!(out, " let __env = self.env();").unwrap();
599 for (i, expr) in js_args_exprs.iter().enumerate() {
601 let expr = expr.replace("self.env()", "__env");
603 writeln!(out, " let arg_{i}: napi::bindgen_prelude::Unknown = {expr};").unwrap();
604 }
605 let tuple_args: Vec<String> = (0..arg_count).map(|i| format!("arg_{i}")).collect();
606 let tuple_str = if arg_count == 1 {
607 format!("({},)", tuple_args[0])
608 } else {
609 format!("({})", tuple_args.join(", "))
610 };
611 writeln!(out, " let result = func.call({tuple_str});").unwrap();
612 }
613
614 writeln!(out, " match result {{").unwrap();
616 writeln!(out, " Err(_) => {ret_ty}::Continue,").unwrap();
617 writeln!(out, " Ok(val) => {{").unwrap();
618 writeln!(
619 out,
620 " if let Ok(s) = val.coerce_to_string().and_then(|s| s.into_utf8()).and_then(|s| s.into_owned()) {{"
621 )
622 .unwrap();
623 writeln!(out, " match s.to_lowercase().as_str() {{").unwrap();
624 writeln!(out, " \"continue\" => {ret_ty}::Continue,").unwrap();
625 writeln!(out, " \"skip\" => {ret_ty}::Skip,").unwrap();
626 writeln!(
627 out,
628 " \"preserve_html\" | \"preservehtml\" => {ret_ty}::PreserveHtml,"
629 )
630 .unwrap();
631 writeln!(
632 out,
633 " other => {ret_ty}::Custom(other.to_string()),"
634 )
635 .unwrap();
636 writeln!(out, " }}").unwrap();
637 writeln!(out, " }} else {{").unwrap();
638 writeln!(out, " {ret_ty}::Continue").unwrap();
639 writeln!(out, " }}").unwrap();
640 writeln!(out, " }}").unwrap();
641 writeln!(out, " }}").unwrap();
642 writeln!(out, " }}").unwrap();
643 writeln!(out).unwrap();
644}
645
646fn build_napi_args(method: &MethodDef) -> Vec<String> {
650 method
651 .params
652 .iter()
653 .map(|p| {
654 if let TypeRef::Named(n) = &p.ty {
655 if n == "NodeContext" {
656 return format!(
657 "nodecontext_to_js_object(&self.env(), {}{}).map(|o| o.to_unknown()).unwrap_or_else(|_| unsafe {{ \
658 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
659 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }})",
660 if p.is_ref { "" } else { "&" },
661 p.name
662 );
663 }
664 }
665 if p.optional && matches!(&p.ty, TypeRef::String) && p.is_ref {
667 return format!(
668 "match {name} {{ \
669 Some(s) => self.env().create_string(s).map(|v| v.to_unknown()).unwrap_or_else(|_| unsafe {{ \
670 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
671 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }}), \
672 None => unsafe {{ \
673 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
674 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }} }}",
675 name = p.name
676 );
677 }
678 if matches!(&p.ty, TypeRef::String) && p.is_ref {
680 return format!(
681 "self.env().create_string({name}).map(|s| s.to_unknown()).unwrap_or_else(|_| unsafe {{ \
682 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
683 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }})",
684 name = p.name
685 );
686 }
687 if matches!(&p.ty, TypeRef::String) {
689 return format!(
690 "self.env().create_string({name}.as_str()).map(|s| s.to_unknown()).unwrap_or_else(|_| unsafe {{ \
691 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
692 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }})",
693 name = p.name
694 );
695 }
696 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool)) {
698 return format!(
699 "unsafe {{ \
700 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), {name}).unwrap_or(std::ptr::null_mut()); \
701 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }}",
702 name = p.name
703 );
704 }
705 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::U32)) {
707 return format!(
708 "self.env().create_uint32({name}).map(|n| n.to_unknown()).unwrap_or_else(|_| unsafe {{ \
709 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
710 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }})",
711 name = p.name
712 );
713 }
714 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Usize)) {
715 return format!(
716 "self.env().create_uint32({name} as u32).map(|n| n.to_unknown()).unwrap_or_else(|_| unsafe {{ \
717 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
718 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }})",
719 name = p.name
720 );
721 }
722 format!(
725 "self.env().create_string(&format!(\"{{:?}}\", {name})).map(|s| s.to_unknown()).unwrap_or_else(|_| unsafe {{ \
726 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
727 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }})",
728 name = p.name
729 )
730 })
731 .collect()
732}
733
734fn to_camel_case(name: &str) -> String {
736 let mut result = String::with_capacity(name.len());
737 let mut capitalize_next = false;
738 for (i, c) in name.chars().enumerate() {
739 if c == '_' {
740 capitalize_next = true;
741 } else if capitalize_next {
742 result.extend(c.to_uppercase());
743 capitalize_next = false;
744 } else if i == 0 {
745 result.extend(c.to_lowercase());
746 } else {
747 result.push(c);
748 }
749 }
750 result
751}
752
753fn param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
755 match ty {
756 TypeRef::Bytes if is_ref => "&[u8]".into(),
757 TypeRef::Bytes => "Vec<u8>".into(),
758 TypeRef::String if is_ref => "&str".into(),
759 TypeRef::String => "String".into(),
760 TypeRef::Path if is_ref => "&std::path::Path".into(),
761 TypeRef::Path => "std::path::PathBuf".into(),
762 TypeRef::Named(n) => {
763 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
764 if is_ref { format!("&{qualified}") } else { qualified }
765 }
766 TypeRef::Vec(inner) => format!("Vec<{}>", param_type(inner, ci, false, tp)),
767 TypeRef::Optional(inner) => format!("Option<{}>", param_type(inner, ci, false, tp)),
768 TypeRef::Primitive(p) => prim(p).into(),
769 TypeRef::Unit => "()".into(),
770 TypeRef::Char => "char".into(),
771 TypeRef::Map(k, v) => format!(
772 "std::collections::HashMap<{}, {}>",
773 param_type(k, ci, false, tp),
774 param_type(v, ci, false, tp)
775 ),
776 TypeRef::Json => "serde_json::Value".into(),
777 TypeRef::Duration => "std::time::Duration".into(),
778 }
779}
780
781fn prim(p: &alef_core::ir::PrimitiveType) -> &'static str {
782 use alef_core::ir::PrimitiveType::*;
783 match p {
784 Bool => "bool",
785 U8 => "u8",
786 U16 => "u16",
787 U32 => "u32",
788 U64 => "u64",
789 I8 => "i8",
790 I16 => "i16",
791 I32 => "i32",
792 I64 => "i64",
793 F32 => "f32",
794 F64 => "f64",
795 Usize => "usize",
796 Isize => "isize",
797 }
798}
799
800pub fn find_bridge_param<'a>(
805 func: &alef_core::ir::FunctionDef,
806 bridges: &'a [TraitBridgeConfig],
807) -> Option<(usize, &'a TraitBridgeConfig)> {
808 for (idx, param) in func.params.iter().enumerate() {
809 let named = match ¶m.ty {
810 TypeRef::Named(n) => Some(n.as_str()),
811 TypeRef::Optional(inner) => {
812 if let TypeRef::Named(n) = inner.as_ref() {
813 Some(n.as_str())
814 } else {
815 None
816 }
817 }
818 _ => None,
819 };
820 for bridge in bridges {
821 if let Some(type_name) = named {
822 if bridge.type_alias.as_deref() == Some(type_name) {
823 return Some((idx, bridge));
824 }
825 }
826 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
827 return Some((idx, bridge));
828 }
829 }
830 }
831 None
832}
833
834#[allow(clippy::too_many_arguments)]
838pub fn gen_bridge_function(
839 func: &alef_core::ir::FunctionDef,
840 bridge_param_idx: usize,
841 bridge_cfg: &TraitBridgeConfig,
842 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
843 _cfg: &alef_codegen::generators::RustBindingConfig<'_>,
844 _adapter_bodies: &alef_codegen::generators::AdapterBodies,
845 opaque_types: &ahash::AHashSet<String>,
846 core_import: &str,
847) -> String {
848 use alef_core::ir::TypeRef;
849
850 let struct_name = format!("Js{}Bridge", bridge_cfg.trait_name);
851 let handle_path = format!("{core_import}::visitor::VisitorHandle");
852 let param_name = &func.params[bridge_param_idx].name;
853 let bridge_param = &func.params[bridge_param_idx];
854 let is_optional = bridge_param.optional || matches!(&bridge_param.ty, TypeRef::Optional(_));
855
856 let mut sig_parts = vec![];
859 for (idx, p) in func.params.iter().enumerate() {
860 if idx == bridge_param_idx {
861 if is_optional {
862 sig_parts.push(format!("{}: Option<napi::bindgen_prelude::Object>", p.name));
863 } else {
864 sig_parts.push(format!("{}: napi::bindgen_prelude::Object", p.name));
865 }
866 } else {
867 let promoted = idx > bridge_param_idx || func.params[..idx].iter().any(|pp| pp.optional);
868 let ty = if p.optional || promoted {
869 format!("Option<{}>", mapper.map_type(&p.ty))
870 } else {
871 mapper.map_type(&p.ty)
872 };
873 sig_parts.push(format!("{}: {}", p.name, ty));
874 }
875 }
876
877 let params_str = sig_parts.join(", ");
878 let return_type = mapper.map_type(&func.return_type);
879 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
880
881 let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
882
883 let bridge_wrap = if is_optional {
885 format!(
886 "let {param_name} = {param_name}.map(|v| {{\n \
887 let bridge = {struct_name}::new(v);\n \
888 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
889 }});"
890 )
891 } else {
892 format!(
893 "let {param_name} = {{\n \
894 let bridge = {struct_name}::new({param_name});\n \
895 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
896 }};"
897 )
898 };
899
900 let serde_bindings: String = func
902 .params
903 .iter()
904 .enumerate()
905 .filter(|(idx, p)| {
906 if *idx == bridge_param_idx {
907 return false;
908 }
909 let named = match &p.ty {
910 TypeRef::Named(n) => Some(n.as_str()),
911 TypeRef::Optional(inner) => {
912 if let TypeRef::Named(n) = inner.as_ref() {
913 Some(n.as_str())
914 } else {
915 None
916 }
917 }
918 _ => None,
919 };
920 named.is_some_and(|n| !opaque_types.contains(n))
921 })
922 .map(|(_, p)| {
923 let name = &p.name;
924 let core_path = format!(
925 "{core_import}::{}",
926 match &p.ty {
927 TypeRef::Named(n) => n.clone(),
928 TypeRef::Optional(inner) =>
929 if let TypeRef::Named(n) = inner.as_ref() {
930 n.clone()
931 } else {
932 String::new()
933 },
934 _ => String::new(),
935 }
936 );
937 if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
938 format!("let {name}_core: Option<{core_path}> = {name}.map(|v| v.into());\n ")
939 } else {
940 format!("let {name}_core: {core_path} = {name}.into();\n ")
941 }
942 })
943 .collect();
944
945 let call_args: Vec<String> = func
947 .params
948 .iter()
949 .enumerate()
950 .map(|(idx, p)| {
951 if idx == bridge_param_idx {
952 return p.name.clone();
953 }
954 match &p.ty {
955 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
956 if p.optional {
957 format!("{}.as_ref().map(|v| &v.inner)", p.name)
958 } else {
959 format!("&{}.inner", p.name)
960 }
961 }
962 TypeRef::Named(_) => format!("{}_core", p.name),
963 TypeRef::Optional(inner) => {
964 if let TypeRef::Named(n) = inner.as_ref() {
965 if opaque_types.contains(n.as_str()) {
966 format!("{}.as_ref().map(|v| &v.inner)", p.name)
967 } else {
968 format!("{}_core", p.name)
969 }
970 } else {
971 p.name.clone()
972 }
973 }
974 TypeRef::String | TypeRef::Char => {
975 if p.is_ref {
976 format!("&{}", p.name)
977 } else {
978 p.name.clone()
979 }
980 }
981 _ => p.name.clone(),
982 }
983 })
984 .collect();
985 let call_args_str = call_args.join(", ");
986
987 let core_fn_path = {
988 let path = func.rust_path.replace('-', "_");
989 if path.starts_with(core_import) {
990 path
991 } else {
992 format!("{core_import}::{}", func.name)
993 }
994 };
995 let core_call = format!("{core_fn_path}({call_args_str})");
996
997 let return_wrap = match &func.return_type {
998 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
999 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
1000 }
1001 TypeRef::Named(_) => "val.into()".to_string(),
1002 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
1003 _ => "val".to_string(),
1004 };
1005
1006 let body = if func.error_type.is_some() {
1007 if return_wrap == "val" {
1008 format!("{bridge_wrap}\n {serde_bindings}{core_call}{err_conv}")
1009 } else {
1010 format!("{bridge_wrap}\n {serde_bindings}{core_call}.map(|val| {return_wrap}){err_conv}")
1011 }
1012 } else {
1013 format!("{bridge_wrap}\n {serde_bindings}{core_call}")
1014 };
1015
1016 let js_name = {
1017 let mut result = String::with_capacity(func.name.len());
1018 let mut capitalize_next = false;
1019 for (i, c) in func.name.chars().enumerate() {
1020 if c == '_' {
1021 capitalize_next = true;
1022 } else if capitalize_next {
1023 result.extend(c.to_uppercase());
1024 capitalize_next = false;
1025 } else if i == 0 {
1026 result.extend(c.to_lowercase());
1027 } else {
1028 result.push(c);
1029 }
1030 }
1031 result
1032 };
1033 let js_name_attr = if js_name != func.name {
1034 format!("(js_name = \"{}\")", js_name)
1035 } else {
1036 String::new()
1037 };
1038
1039 let mut out = String::with_capacity(1024);
1040 if func.error_type.is_some() {
1041 writeln!(out, "#[allow(clippy::missing_errors_doc)]").ok();
1042 }
1043 writeln!(out, "#[napi{js_name_attr}]").ok();
1044 let func_name = &func.name;
1045 writeln!(out, "pub fn {func_name}({params_str}) -> {ret} {{").ok();
1046 writeln!(out, " {body}").ok();
1047 writeln!(out, "}}").ok();
1048
1049 out
1050}