1use alef_codegen::generators::trait_bridge::{
7 BridgeOutput, TraitBridgeGenerator, TraitBridgeSpec, bridge_param_type as param_type, gen_bridge_all,
8 host_function_path, to_camel_case, visitor_param_type,
9};
10use alef_core::config::TraitBridgeConfig;
11use alef_core::ir::{ApiSurface, MethodDef, TypeDef, TypeRef};
12use std::collections::HashMap;
13
14pub use alef_codegen::generators::trait_bridge::find_bridge_param;
19
20pub fn find_options_field_binding<'a>(
23 func: &alef_core::ir::FunctionDef,
24 bridges: &'a [TraitBridgeConfig],
25) -> Option<(usize, &'a TraitBridgeConfig)> {
26 for bridge in bridges {
27 if bridge.bind_via != alef_core::config::BridgeBinding::OptionsField {
28 continue;
29 }
30 if let Some(options_type) = &bridge.options_type {
31 for (idx, param) in func.params.iter().enumerate() {
32 let matches = match ¶m.ty {
34 alef_core::ir::TypeRef::Named(n) => n == options_type,
35 alef_core::ir::TypeRef::Optional(inner) => {
36 if let alef_core::ir::TypeRef::Named(n) = inner.as_ref() {
37 n == options_type
38 } else {
39 false
40 }
41 }
42 _ => false,
43 };
44 if matches {
45 return Some((idx, bridge));
46 }
47 }
48 }
49 }
50 None
51}
52
53pub struct NapiBridgeGenerator {
56 pub core_import: String,
58 pub type_paths: HashMap<String, String>,
60 pub error_type: String,
62}
63
64impl TraitBridgeGenerator for NapiBridgeGenerator {
65 fn foreign_object_type(&self) -> &str {
66 "napi::bindgen_prelude::Object<'static>"
67 }
68
69 fn bridge_imports(&self) -> Vec<String> {
70 vec![
71 "napi::bindgen_prelude::{JsObjectValue, ToNapiValue, Unknown, Object}".to_string(),
72 "napi::JsValue".to_string(),
73 "std::sync::Arc".to_string(),
74 ]
75 }
76
77 fn gen_sync_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
78 let name = &method.name;
79 let has_error = method.error_type.is_some();
80
81 let js_args_exprs = build_napi_args(method);
83 let inner_tuple_ty = unknown_tuple_type(js_args_exprs.len());
84 let args_tuple_ty = if js_args_exprs.is_empty() {
85 inner_tuple_ty.clone()
86 } else {
87 format!("napi::bindgen_prelude::FnArgs<{inner_tuple_ty}>")
88 };
89
90 let empty_args = js_args_exprs.is_empty();
91 let tuple_args = if empty_args {
92 String::new()
93 } else if js_args_exprs.len() == 1 {
94 format!("({},)", js_args_exprs[0])
95 } else {
96 format!("({})", js_args_exprs.join(", "))
97 };
98
99 let error_lookup =
100 spec.make_error("format!(\"Method '{}' not found on bridge object: {}\", self.cached_name, e)");
101 let error_call = spec.make_error(&format!(
102 "format!(\"Plugin '{{}}' method '{}' failed: {{}}\", self.cached_name, e)",
103 name
104 ));
105 let error_coercion = spec.make_error(&format!(
106 "format!(\"Failed to extract return value from method '{}': {{}}\", e)",
107 name
108 ));
109 let error_parse = spec.make_error(&format!(
110 "format!(\"Failed to parse return value for method '{}'\", self.cached_name)",
111 name
112 ));
113
114 if matches!(method.return_type, TypeRef::Unit) {
115 crate::template_env::render(
116 "sync_method_unit_return.jinja",
117 minijinja::context! {
118 method_name => name,
119 args_tuple_ty => args_tuple_ty,
120 has_error => has_error,
121 empty_args => empty_args,
122 tuple_args => tuple_args,
123 error_lookup => error_lookup,
124 error_call => error_call,
125 },
126 )
127 } else {
128 crate::template_env::render(
129 "sync_method_non_unit_return.jinja",
130 minijinja::context! {
131 method_name => name,
132 args_tuple_ty => args_tuple_ty,
133 has_error => has_error,
134 empty_args => empty_args,
135 tuple_args => tuple_args,
136 error_lookup => error_lookup,
137 error_call => error_call,
138 error_coercion => error_coercion,
139 error_parse => error_parse,
140 },
141 )
142 }
143 }
144
145 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
146 let name = &method.name;
147
148 let js_args_exprs = build_napi_args(method);
150 let inner_tuple_ty = unknown_tuple_type(js_args_exprs.len());
151 let args_tuple_ty = if js_args_exprs.is_empty() {
152 inner_tuple_ty.clone()
153 } else {
154 format!("napi::bindgen_prelude::FnArgs<{inner_tuple_ty}>")
155 };
156
157 let empty_args = js_args_exprs.is_empty();
158 let tuple_args = if empty_args {
159 String::new()
160 } else if js_args_exprs.len() == 1 {
161 format!("({},)", js_args_exprs[0])
162 } else {
163 format!("({})", js_args_exprs.join(", "))
164 };
165
166 let error_lookup = spec.make_error("format!(\"Method '{}' not found on bridge object: {}\", cached_name, e)");
167 let error_call = spec.make_error(&format!(
168 "format!(\"Plugin '{{}}' method '{}' failed: {{}}\", cached_name, e)",
169 name
170 ));
171 let error_coercion = spec.make_error(&format!(
172 "format!(\"Failed to extract return value from method '{}': {{}}\", e)",
173 name
174 ));
175 let error_parse = spec.make_error(&format!(
176 "\"Failed to parse return value for method '{}'\".to_string()",
177 name
178 ));
179
180 if matches!(method.return_type, TypeRef::Unit) {
181 crate::template_env::render(
182 "async_method_unit_return.jinja",
183 minijinja::context! {
184 method_name => name,
185 args_tuple_ty => args_tuple_ty,
186 empty_args => empty_args,
187 tuple_args => tuple_args,
188 error_lookup => error_lookup,
189 error_call => error_call,
190 },
191 )
192 } else {
193 crate::template_env::render(
194 "async_method_non_unit_return.jinja",
195 minijinja::context! {
196 method_name => name,
197 args_tuple_ty => args_tuple_ty,
198 empty_args => empty_args,
199 tuple_args => tuple_args,
200 error_lookup => error_lookup,
201 error_call => error_call,
202 error_coercion => error_coercion,
203 error_parse => error_parse,
204 },
205 )
206 }
207 }
208
209 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
210 let wrapper = spec.wrapper_name();
211 let required_methods = spec
212 .required_methods()
213 .iter()
214 .map(|m| {
215 minijinja::context! {
216 name => &m.name,
217 }
218 })
219 .collect::<Vec<_>>();
220
221 crate::template_env::render(
222 "trait_bridge_constructor.jinja",
223 minijinja::context! {
224 wrapper_name => wrapper,
225 required_methods => required_methods,
226 },
227 )
228 }
229
230 fn gen_unregistration_fn(&self, spec: &TraitBridgeSpec) -> String {
231 let Some(unregister_fn) = spec.bridge_config.unregister_fn.as_deref() else {
232 return String::new();
233 };
234 let host_path = host_function_path(spec, unregister_fn);
235 let camel = to_camel_case(unregister_fn);
236 crate::template_env::render(
237 "unregistration_fn.jinja",
238 minijinja::context! {
239 unregister_fn => unregister_fn,
240 camel_fn_name => camel,
241 host_path => host_path,
242 },
243 )
244 }
245
246 fn gen_clear_fn(&self, spec: &TraitBridgeSpec) -> String {
247 let Some(clear_fn) = spec.bridge_config.clear_fn.as_deref() else {
248 return String::new();
249 };
250 let host_path = host_function_path(spec, clear_fn);
251 let camel = to_camel_case(clear_fn);
252 crate::template_env::render(
253 "clear_fn.jinja",
254 minijinja::context! {
255 clear_fn => clear_fn,
256 camel_fn_name => camel,
257 host_path => host_path,
258 },
259 )
260 }
261
262 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
263 let Some(register_fn) = spec.bridge_config.register_fn.as_deref() else {
264 return String::new();
265 };
266 let Some(registry_getter) = spec.bridge_config.registry_getter.as_deref() else {
267 return String::new();
268 };
269 let wrapper = spec.wrapper_name();
270 let trait_path = spec.trait_path();
271
272 let extra = spec
273 .bridge_config
274 .register_extra_args
275 .as_deref()
276 .map(|a| format!(", {a}"))
277 .unwrap_or_default();
278
279 crate::template_env::render(
280 "registration_fn.jinja",
281 minijinja::context! {
282 register_fn => register_fn,
283 wrapper => wrapper,
284 trait_path => trait_path,
285 registry_getter => registry_getter,
286 extra_args => extra,
287 },
288 )
289 }
290}
291
292pub fn gen_trait_bridge(
294 trait_type: &TypeDef,
295 bridge_cfg: &TraitBridgeConfig,
296 core_import: &str,
297 error_type: &str,
298 error_constructor: &str,
299 api: &ApiSurface,
300) -> BridgeOutput {
301 let type_paths: HashMap<String, String> = api
303 .types
304 .iter()
305 .map(|t| (t.name.clone(), t.rust_path.replace('-', "_")))
306 .chain(
307 api.enums
308 .iter()
309 .map(|e| (e.name.clone(), e.rust_path.replace('-', "_"))),
310 )
311 .collect();
312
313 let is_visitor_bridge = bridge_cfg.type_alias.is_some()
315 && bridge_cfg.register_fn.is_none()
316 && bridge_cfg.super_trait.is_none()
317 && trait_type.methods.iter().all(|m| m.has_default_impl);
318
319 if is_visitor_bridge {
320 let struct_name = format!("Js{}Bridge", bridge_cfg.trait_name);
321 let trait_path = trait_type.rust_path.replace('-', "_");
322 let code = gen_visitor_bridge(
323 trait_type,
324 bridge_cfg,
325 &struct_name,
326 &trait_path,
327 core_import,
328 &type_paths,
329 );
330 BridgeOutput { imports: vec![], code }
331 } else {
332 let generator = NapiBridgeGenerator {
334 core_import: core_import.to_string(),
335 type_paths: type_paths.clone(),
336 error_type: error_type.to_string(),
337 };
338 let spec = TraitBridgeSpec {
339 trait_def: trait_type,
340 bridge_config: bridge_cfg,
341 core_import,
342 wrapper_prefix: "Js",
343 type_paths,
344 error_type: error_type.to_string(),
345 error_constructor: error_constructor.to_string(),
346 };
347 gen_bridge_all(&spec, &generator)
348 }
349}
350
351fn gen_visitor_bridge(
356 trait_type: &TypeDef,
357 _bridge_cfg: &TraitBridgeConfig,
358 struct_name: &str,
359 trait_path: &str,
360 core_crate: &str,
361 type_paths: &HashMap<String, String>,
362) -> String {
363 let mut method_impls = String::with_capacity(4096);
364 for method in &trait_type.methods {
365 if method.trait_source.is_some() {
366 continue;
367 }
368 gen_visitor_method_napi(&mut method_impls, method, trait_path, core_crate, type_paths);
369 }
370
371 crate::template_env::render(
372 "visitor_bridge.jinja",
373 minijinja::context! {
374 core_crate => core_crate,
375 struct_name => struct_name,
376 trait_path => trait_path,
377 method_impls => method_impls,
378 },
379 )
380}
381
382fn unknown_tuple_type(count: usize) -> String {
384 if count == 0 {
385 return "()".to_string();
386 }
387 let parts = vec!["napi::bindgen_prelude::Unknown"; count];
388 format!("({}{})", parts.join(", "), if count == 1 { "," } else { "" })
389}
390
391fn gen_visitor_method_napi(
393 out: &mut String,
394 method: &MethodDef,
395 _trait_path: &str,
396 _core_crate: &str,
397 type_paths: &HashMap<String, String>,
398) {
399 let name = &method.name;
400 let js_method_name = to_camel_case(name);
401
402 let mut sig_parts = vec!["&mut self".to_string()];
403 for p in &method.params {
404 let ty_str = visitor_param_type(&p.ty, p.is_ref, p.optional, type_paths);
405 sig_parts.push(format!("{}: {}", p.name, ty_str));
406 }
407 let signature = sig_parts.join(", ");
408
409 let return_type = match &method.return_type {
410 TypeRef::Named(n) => type_paths
411 .get(n.as_str())
412 .map(|p| p.replace('-', "_"))
413 .unwrap_or_else(|| n.clone()),
414 other => param_type(other, "", false, type_paths),
415 };
416
417 let arg_count = method.params.len();
418 let empty_args = arg_count == 0;
419 let inner_tuple_ty = unknown_tuple_type(arg_count);
420 let args_tuple_ty = if empty_args {
421 inner_tuple_ty
422 } else {
423 format!("napi::bindgen_prelude::FnArgs<{inner_tuple_ty}>")
424 };
425
426 let js_args_exprs = build_napi_args(method);
427 let arg_exprs: Vec<String> = js_args_exprs
428 .iter()
429 .map(|expr| expr.replace("self.env()", "__env"))
430 .collect();
431
432 let tuple_args = if arg_count == 1 {
433 "(arg_0,)".to_string()
434 } else if arg_count > 0 {
435 let arg_names: Vec<String> = (0..arg_count).map(|i| format!("arg_{i}")).collect();
436 format!("({})", arg_names.join(", "))
437 } else {
438 String::new()
439 };
440
441 out.push_str(&crate::template_env::render(
442 "visitor_method.jinja",
443 minijinja::context! {
444 method_name => name,
445 js_method_name => js_method_name,
446 signature => signature,
447 return_type => return_type,
448 empty_args => empty_args,
449 arg_exprs => arg_exprs,
450 tuple_args => tuple_args,
451 args_tuple_ty => args_tuple_ty,
452 },
453 ));
454}
455
456fn build_napi_args(method: &MethodDef) -> Vec<String> {
460 method
461 .params
462 .iter()
463
464 .map(|p| {
465 if let TypeRef::Named(n) = &p.ty {
466 if n == "NodeContext" {
467 return format!(
468 "match nodecontext_to_js_object(&self.env(), {}{}) {{ Ok(o) => o.to_unknown(), Err(_) => unsafe {{ \
469 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
470 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }} \
471 }}",
472 if p.is_ref { "" } else { "&" },
473 p.name
474 );
475 }
476 }
477 if p.optional && matches!(&p.ty, TypeRef::String) && p.is_ref {
479 return format!(
480 "match {name} {{ \
481 Some(s) => match self.env().create_string(s) {{ \
482 Ok(v) => v.to_unknown(), \
483 Err(_) => unsafe {{ \
484 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
485 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }} \
486 }}, \
487 None => unsafe {{ \
488 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
489 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }} \
490 }}",
491 name = p.name
492 );
493 }
494 if matches!(&p.ty, TypeRef::String) && p.is_ref {
496 return format!(
497 "match self.env().create_string({name}) {{ \
498 Ok(s) => s.to_unknown(), \
499 Err(_) => unsafe {{ \
500 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
501 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }} \
502 }}",
503 name = p.name
504 );
505 }
506 if matches!(&p.ty, TypeRef::String) {
508 return format!(
509 "match self.env().create_string({name}.as_str()) {{ \
510 Ok(s) => s.to_unknown(), \
511 Err(_) => unsafe {{ \
512 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
513 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }} \
514 }}",
515 name = p.name
516 );
517 }
518 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool)) {
520 return format!(
521 "unsafe {{ \
522 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), {name}).unwrap_or(std::ptr::null_mut()); \
523 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }}",
524 name = p.name
525 );
526 }
527 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::U32)) {
529 return format!(
530 "match self.env().create_uint32({name}) {{ Ok(n) => n.to_unknown(), Err(_) => unsafe {{ \
531 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
532 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }} \
533 }}",
534 name = p.name
535 );
536 }
537 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Usize)) {
538 return format!(
539 "match self.env().create_uint32({name} as u32) {{ Ok(n) => n.to_unknown(), Err(_) => unsafe {{ \
540 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
541 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }} \
542 }}",
543 name = p.name
544 );
545 }
546 format!(
549 "match self.env().create_string(&format!(\"{{:?}}\", {name})) {{ Ok(s) => s.to_unknown(), Err(_) => unsafe {{ \
550 let r = napi::bindgen_prelude::ToNapiValue::to_napi_value(self.env().raw(), napi::bindgen_prelude::Null).unwrap_or(std::ptr::null_mut()); \
551 napi::bindgen_prelude::Unknown::from_raw_unchecked(self.env().raw(), r) }} \
552 }}",
553 name = p.name
554 )
555 })
556 .collect()
557}
558
559#[allow(clippy::too_many_arguments)]
563pub fn gen_bridge_function(
564 func: &alef_core::ir::FunctionDef,
565 bridge_param_idx: usize,
566 bridge_cfg: &TraitBridgeConfig,
567 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
568 _cfg: &alef_codegen::generators::RustBindingConfig<'_>,
569 _adapter_bodies: &alef_codegen::generators::AdapterBodies,
570 opaque_types: &ahash::AHashSet<String>,
571 core_import: &str,
572) -> String {
573 use alef_core::ir::TypeRef;
574
575 let struct_name = format!("Js{}Bridge", bridge_cfg.trait_name);
576 let handle_path = format!("{core_import}::visitor::VisitorHandle");
577 let param_name = &func.params[bridge_param_idx].name;
578 let bridge_param = &func.params[bridge_param_idx];
579 let is_optional = bridge_param.optional || matches!(&bridge_param.ty, TypeRef::Optional(_));
580
581 let is_options_field_binding = matches!(bridge_cfg.bind_via, alef_core::config::BridgeBinding::OptionsField);
583
584 let options_param_idx = if is_options_field_binding {
586 func.params.iter().enumerate().find(|(_, p)| {
587 matches!(&p.ty, TypeRef::Named(n) if bridge_cfg.options_type.as_ref().is_some_and(|opt_type| n == opt_type))
588 }).map(|(i, _)| i)
589 } else {
590 None
591 };
592
593 let mut sig_parts = vec![];
596 for (idx, p) in func.params.iter().enumerate() {
597 if is_options_field_binding && Some(idx) == options_param_idx {
598 let ty = if p.optional || (idx > 0 && func.params[..idx].iter().any(|pp| pp.optional)) {
600 format!("Option<{}>", mapper.map_type(&p.ty))
601 } else {
602 mapper.map_type(&p.ty)
603 };
604 sig_parts.push(format!("{}: {}", p.name, ty));
605 } else if idx == bridge_param_idx {
606 if is_optional {
607 sig_parts.push(format!("{}: Option<napi::bindgen_prelude::Object>", p.name));
608 } else {
609 sig_parts.push(format!("{}: napi::bindgen_prelude::Object", p.name));
610 }
611 } else {
612 let promoted = idx > bridge_param_idx || func.params[..idx].iter().any(|pp| pp.optional);
613 let ty = if p.optional || promoted {
614 format!("Option<{}>", mapper.map_type(&p.ty))
615 } else {
616 mapper.map_type(&p.ty)
617 };
618 sig_parts.push(format!("{}: {}", p.name, ty));
619 }
620 }
621
622 let params_str = sig_parts.join(", ");
623 let return_type = mapper.map_type(&func.return_type);
624 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
625
626 let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
627
628 let bridge_wrap = if is_optional {
630 format!(
631 "let {param_name} = {param_name}.map(|v| {{\n \
632 let bridge = {struct_name}::new(v);\n \
633 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
634 }});"
635 )
636 } else {
637 format!(
638 "let {param_name} = {{\n \
639 let bridge = {struct_name}::new({param_name});\n \
640 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
641 }};"
642 )
643 };
644
645 let serde_bindings: String = func
647 .params
648 .iter()
649 .enumerate()
650 .filter(|(idx, p)| {
651 if *idx == bridge_param_idx {
652 return false;
653 }
654 let named = match &p.ty {
655 TypeRef::Named(n) => Some(n.as_str()),
656 TypeRef::Optional(inner) => {
657 if let TypeRef::Named(n) = inner.as_ref() {
658 Some(n.as_str())
659 } else {
660 None
661 }
662 }
663 _ => None,
664 };
665 named.is_some_and(|n| !opaque_types.contains(n))
666 })
667 .map(|(_, p)| {
668 let name = &p.name;
669 let core_path = format!(
670 "{core_import}::{}",
671 match &p.ty {
672 TypeRef::Named(n) => n.clone(),
673 TypeRef::Optional(inner) =>
674 if let TypeRef::Named(n) = inner.as_ref() {
675 n.clone()
676 } else {
677 String::new()
678 },
679 _ => String::new(),
680 }
681 );
682 if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
683 format!("let {name}_core: Option<{core_path}> = {name}.map(|v| v.into());\n ")
684 } else {
685 format!("let {name}_core: {core_path} = {name}.into();\n ")
686 }
687 })
688 .collect();
689
690 let call_args: Vec<String> = func
692 .params
693 .iter()
694 .enumerate()
695 .map(|(idx, p)| {
696 if idx == bridge_param_idx {
697 return p.name.clone();
698 }
699 match &p.ty {
700 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
701 if p.optional {
702 format!("{}.as_ref().map(|v| &v.inner)", p.name)
703 } else {
704 format!("&{}.inner", p.name)
705 }
706 }
707 TypeRef::Named(_) => format!("{}_core", p.name),
708 TypeRef::Optional(inner) => {
709 if let TypeRef::Named(n) = inner.as_ref() {
710 if opaque_types.contains(n.as_str()) {
711 format!("{}.as_ref().map(|v| &v.inner)", p.name)
712 } else {
713 format!("{}_core", p.name)
714 }
715 } else {
716 p.name.clone()
717 }
718 }
719 TypeRef::String | TypeRef::Char => {
720 if p.is_ref {
721 format!("&{}", p.name)
722 } else {
723 p.name.clone()
724 }
725 }
726 _ => p.name.clone(),
727 }
728 })
729 .collect();
730 let call_args_str = call_args.join(", ");
731
732 let core_fn_path = {
733 let path = func.rust_path.replace('-', "_");
734 if path.starts_with(core_import) {
735 path
736 } else {
737 format!("{core_import}::{}", func.name)
738 }
739 };
740 let core_call = format!("{core_fn_path}({call_args_str})");
741
742 let return_wrap = match &func.return_type {
743 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
744 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
745 }
746 TypeRef::Named(_) => "val.into()".to_string(),
747 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
748 _ => "val".to_string(),
749 };
750
751 let body = if func.error_type.is_some() {
752 if return_wrap == "val" {
753 format!("{bridge_wrap}\n {serde_bindings}{core_call}{err_conv}")
754 } else {
755 format!("{bridge_wrap}\n {serde_bindings}{core_call}.map(|val| {return_wrap}){err_conv}")
756 }
757 } else {
758 format!("{bridge_wrap}\n {serde_bindings}{core_call}")
759 };
760
761 let js_name = {
762 let mut result = String::with_capacity(func.name.len());
763 let mut capitalize_next = false;
764 for (i, c) in func.name.chars().enumerate() {
765 if c == '_' {
766 capitalize_next = true;
767 } else if capitalize_next {
768 result.extend(c.to_uppercase());
769 capitalize_next = false;
770 } else if i == 0 {
771 result.extend(c.to_lowercase());
772 } else {
773 result.push(c);
774 }
775 }
776 result
777 };
778 let js_name_attr = if js_name != func.name {
779 format!("(js_name = \"{}\")", js_name)
780 } else {
781 String::new()
782 };
783
784 let func_name = &func.name;
785 crate::template_env::render(
786 "bridge_function.jinja",
787 minijinja::context! {
788 has_error => func.error_type.is_some(),
789 js_name_attr => js_name_attr,
790 func_name => func_name,
791 params_str => params_str,
792 ret => ret,
793 body => body,
794 },
795 )
796}
797
798#[allow(clippy::too_many_arguments)]
802pub fn gen_options_field_bridge_function(
803 func: &alef_core::ir::FunctionDef,
804 options_param_idx: usize,
805 bridge_cfg: &TraitBridgeConfig,
806 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
807 _cfg: &alef_codegen::generators::RustBindingConfig<'_>,
808 opaque_types: &ahash::AHashSet<String>,
809 core_import: &str,
810) -> String {
811 use alef_core::ir::TypeRef;
812
813 let struct_name = format!("Js{}Bridge", bridge_cfg.trait_name);
814 let handle_path = format!("{core_import}::visitor::VisitorHandle");
815 let options_param = &func.params[options_param_idx];
816 let options_name = &options_param.name;
817
818 let is_param_optional = true;
823
824 let ir_param_optional = matches!(&options_param.ty, TypeRef::Optional(_));
826
827 let params_str = {
829 let mut sig_parts = vec![];
830 for (i, p) in func.params.iter().enumerate() {
831 let ty = mapper.map_type(&p.ty);
832 if i == options_param_idx && !ir_param_optional {
833 sig_parts.push(format!("{}: Option<{ty}>", p.name));
834 } else {
835 sig_parts.push(format!("{}: {ty}", p.name));
836 }
837 }
838 sig_parts.join(", ")
839 };
840
841 let return_type = mapper.map_type(&func.return_type);
842 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
843
844 let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
845
846 let visitor_extract = if is_param_optional {
848 format!(
849 "let visitor_handle = {options_name}.as_ref().and_then(|o| o.visitor.clone()).map(|v| {{\n \
850 let bridge = {struct_name}::new(v);\n \
851 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n\
852 }});"
853 )
854 } else {
855 format!(
856 "let visitor_handle = {options_name}.visitor.clone().map(|v| {{\n \
857 let bridge = {struct_name}::new(v);\n \
858 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n\
859 }});"
860 )
861 };
862
863 let options_convert = if is_param_optional {
868 format!(
869 "let {options_name}_core: Option<{core_import}::ConversionOptions> = {options_name}.map(|mut o| {{\n \
870 o.visitor = None;\n \
871 let mut result: {core_import}::ConversionOptions = o.into();\n \
872 result.visitor = visitor_handle.clone();\n \
873 result\n \
874 }});"
875 )
876 } else {
877 format!(
878 "let {options_name}_core: Option<{core_import}::ConversionOptions> = {{\n \
879 let mut o = {options_name}.clone();\n \
880 o.visitor = None;\n \
881 let mut result: {core_import}::ConversionOptions = o.into();\n \
882 result.visitor = visitor_handle.clone();\n \
883 Some(result)\n \
884 }};"
885 )
886 };
887
888 let call_args: String = func
890 .params
891 .iter()
892 .enumerate()
893 .map(|(idx, p)| {
894 if idx == options_param_idx {
895 format!("{options_name}_core")
896 } else {
897 match &p.ty {
898 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
899 if p.optional {
900 format!("{}.as_ref().map(|v| &v.inner)", p.name)
901 } else {
902 format!("&{}.inner", p.name)
903 }
904 }
905 TypeRef::Named(_) => format!("{}.into()", p.name),
906 TypeRef::Optional(inner) => {
907 if let TypeRef::Named(n) = inner.as_ref() {
908 if opaque_types.contains(n.as_str()) {
909 format!("{}.as_ref().map(|v| &v.inner)", p.name)
910 } else {
911 format!("{}.map(Into::into)", p.name)
912 }
913 } else {
914 p.name.clone()
915 }
916 }
917 TypeRef::String | TypeRef::Char => {
918 if p.is_ref {
919 format!("&{}", p.name)
920 } else {
921 p.name.clone()
922 }
923 }
924 _ => p.name.clone(),
925 }
926 }
927 })
928 .collect::<Vec<_>>()
929 .join(", ");
930
931 let core_fn_path = {
932 let path = func.rust_path.replace('-', "_");
933 if path.starts_with(core_import) {
934 path
935 } else {
936 format!("{core_import}::{}", func.name)
937 }
938 };
939 let core_call = format!("{core_fn_path}({call_args})");
940
941 let return_wrap = match &func.return_type {
942 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
943 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
944 }
945 TypeRef::Named(_) => "val.into()".to_string(),
946 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
947 _ => "val".to_string(),
948 };
949
950 let body = if func.error_type.is_some() {
951 if return_wrap == "val" {
952 format!("{visitor_extract}\n {options_convert}\n {core_call}{err_conv}")
953 } else {
954 format!("{visitor_extract}\n {options_convert}\n {core_call}.map(|val| {return_wrap}){err_conv}")
955 }
956 } else {
957 format!("{visitor_extract}\n {options_convert}\n {core_call}")
958 };
959
960 let mut out = String::with_capacity(1024);
961 if func.error_type.is_some() {
962 out.push_str("#[allow(clippy::missing_errors_doc)]\n");
963 }
964 out.push_str("#[napi]\n");
965 let func_name = &func.name;
966 out.push_str(&crate::template_env::render(
967 "trait_bridge_fn_wrapper.jinja",
968 minijinja::context! {
969 func_name => func_name,
970 params_str => params_str,
971 return_type => ret,
972 body => body,
973 },
974 ));
975
976 out
977}