1use alef_codegen::generators::trait_bridge::{BridgeOutput, 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 PhpBridgeGenerator {
15 pub core_import: String,
17 pub type_paths: HashMap<String, String>,
19 pub error_type: String,
21}
22
23impl TraitBridgeGenerator for PhpBridgeGenerator {
24 fn foreign_object_type(&self) -> &str {
25 "&mut ext_php_rs::types::ZendObject"
26 }
27
28 fn bridge_imports(&self) -> Vec<String> {
29 vec!["std::rc::Rc".to_string(), "std::cell::RefCell".to_string()]
30 }
31
32 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
33 let name = &method.name;
34 let mut out = String::with_capacity(512);
35
36 writeln!(
38 out,
39 "// SAFETY: PHP objects are single-threaded; method calls are safe within a request."
40 )
41 .ok();
42
43 let has_args = !method.params.is_empty();
44 if has_args {
45 writeln!(out, "let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").ok();
46 for p in &method.params {
47 writeln!(
48 out,
49 "args.push(ext_php_rs::types::Zval::try_from({}).unwrap_or_default());",
50 p.name
51 )
52 .ok();
53 }
54 }
55
56 let args_expr = if has_args {
57 "args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect()"
58 } else {
59 "vec![]"
60 };
61
62 writeln!(out, "let result = self.inner.try_call_method(\"{name}\", {args_expr});").ok();
63 writeln!(out, "match result {{").ok();
64 writeln!(
65 out,
66 " Ok(val) => val.string().unwrap_or_default().parse().unwrap_or_default(),"
67 )
68 .ok();
69 writeln!(out, " Err(_) => Default::default(),").ok();
70 writeln!(out, "}}").ok();
71
72 out
73 }
74
75 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
76 let name = &method.name;
77 let mut out = String::with_capacity(1024);
78
79 writeln!(out, "let inner_obj = self.inner.clone();").ok();
80 writeln!(out, "let cached_name = self.cached_name.clone();").ok();
81
82 for p in &method.params {
84 match &p.ty {
85 TypeRef::String => {
86 writeln!(out, "let {} = {}.clone();", p.name, p.name).ok();
87 }
88 _ => {
89 writeln!(out, "let {} = {};", p.name, p.name).ok();
90 }
91 }
92 }
93
94 writeln!(out).ok();
95 writeln!(out, "Box::pin(async move {{").ok();
96 writeln!(out, " // SAFETY: PHP objects are single-threaded within a request.").ok();
97 writeln!(out, " // The block_on executes within the async runtime.").ok();
98 writeln!(out, " let result = WORKER_RUNTIME.block_on(async {{").ok();
99
100 let has_args = !method.params.is_empty();
101 if has_args {
102 writeln!(out, " let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").ok();
103 for p in &method.params {
104 writeln!(
105 out,
106 " args.push(ext_php_rs::types::Zval::try_from({}).unwrap_or_default());",
107 p.name
108 )
109 .ok();
110 }
111 }
112
113 let args_expr = if has_args {
114 "args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect()"
115 } else {
116 "vec![]"
117 };
118
119 writeln!(
120 out,
121 " match inner_obj.try_call_method(\"{name}\", {args_expr}) {{"
122 )
123 .ok();
124 writeln!(
125 out,
126 " Ok(val) => val.string().unwrap_or_default().parse().unwrap_or_default(),"
127 )
128 .ok();
129 writeln!(
130 out,
131 " Err(e) => Err({}::KreuzbergError::Plugin {{",
132 spec.core_import
133 )
134 .ok();
135 writeln!(
136 out,
137 " message: format!(\"Plugin '{{}}' method '{name}' failed: {{}}\", cached_name, e),"
138 )
139 .ok();
140 writeln!(out, " plugin_name: cached_name.clone(),").ok();
141 writeln!(out, " }}),").ok();
142 writeln!(out, " }}").ok();
143 writeln!(out, " }});").ok();
144 writeln!(out, " result").ok();
145 writeln!(out, "}})").ok();
146
147 out
148 }
149
150 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
151 let wrapper = spec.wrapper_name();
152 let mut out = String::with_capacity(512);
153
154 writeln!(out, "impl {wrapper} {{").ok();
155 writeln!(out, " /// Create a new bridge wrapping a PHP object.").ok();
156 writeln!(out, " ///").ok();
157 writeln!(
158 out,
159 " /// Validates that the PHP object provides all required methods."
160 )
161 .ok();
162 writeln!(
163 out,
164 " pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
165 )
166 .ok();
167
168 for req_method in spec.required_methods() {
170 writeln!(
171 out,
172 " debug_assert!(php_obj.get_property(\"{}\").is_some(),",
173 req_method.name
174 )
175 .ok();
176 writeln!(
177 out,
178 " \"PHP object missing required method: {}\");",
179 req_method.name
180 )
181 .ok();
182 }
183
184 writeln!(out, " let cached_name = php_obj").ok();
186 writeln!(out, " .try_call_method(\"name\", vec![])").ok();
187 writeln!(out, " .ok()").ok();
188 writeln!(out, " .and_then(|v| v.string())").ok();
189 writeln!(out, " .unwrap_or(\"unknown\".into())").ok();
190 writeln!(out, " .to_string();").ok();
191
192 writeln!(out).ok();
193 writeln!(out, " Self {{").ok();
194 writeln!(out, " inner: php_obj,").ok();
195 writeln!(out, " cached_name,").ok();
196 writeln!(out, " }}").ok();
197 writeln!(out, " }}").ok();
198 writeln!(out, "}}").ok();
199
200 out
201 }
202
203 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
204 let Some(register_fn) = spec.bridge_config.register_fn.as_deref() else {
205 return String::new();
206 };
207 let Some(registry_getter) = spec.bridge_config.registry_getter.as_deref() else {
208 return String::new();
209 };
210 let wrapper = spec.wrapper_name();
211 let trait_path = spec.trait_path();
212
213 let mut out = String::with_capacity(1024);
214
215 writeln!(out, "#[php_function]").ok();
216 writeln!(
217 out,
218 "pub fn {register_fn}(backend: &mut ext_php_rs::types::ZendObject) -> ext_php_rs::prelude::PhpResult<()> {{"
219 )
220 .ok();
221
222 let req_methods: Vec<&MethodDef> = spec.required_methods();
224 if !req_methods.is_empty() {
225 for method in &req_methods {
226 writeln!(out, " if backend.get_property(\"{}\").is_none() {{", method.name).ok();
227 writeln!(out, " return Err(ext_php_rs::exception::PhpException::default(").ok();
228 writeln!(
229 out,
230 " format!(\"Backend missing required method: {{}}\", \"{}\")",
231 method.name
232 )
233 .ok();
234 writeln!(out, " ).into());").ok();
235 writeln!(out, " }}").ok();
236 }
237 }
238
239 writeln!(out).ok();
240 writeln!(out, " let wrapper = {wrapper}::new(backend);").ok();
241 writeln!(
242 out,
243 " let arc: Rc<RefCell<dyn {trait_path}>> = Rc::new(RefCell::new(wrapper));"
244 )
245 .ok();
246 writeln!(out).ok();
247
248 writeln!(out, " let registry = {registry_getter}();").ok();
249 writeln!(out, " let mut registry = registry;").ok();
250 writeln!(
251 out,
252 " registry.register(arc).map_err(|e| ext_php_rs::exception::PhpException::default("
253 )
254 .ok();
255 writeln!(out, " format!(\"Failed to register backend: {{}}\", e)").ok();
256 writeln!(out, " ))?;").ok();
257 writeln!(out, " Ok(())").ok();
258 writeln!(out, "}}").ok();
259
260 out
261 }
262}
263
264pub fn gen_trait_bridge(
266 trait_type: &TypeDef,
267 bridge_cfg: &TraitBridgeConfig,
268 core_import: &str,
269 error_type: &str,
270 api: &ApiSurface,
271) -> BridgeOutput {
272 let type_paths: HashMap<String, String> = api
274 .types
275 .iter()
276 .map(|t| (t.name.clone(), t.rust_path.replace('-', "_")))
277 .chain(
278 api.enums
279 .iter()
280 .map(|e| (e.name.clone(), e.rust_path.replace('-', "_"))),
281 )
282 .collect();
283
284 let is_visitor_bridge = bridge_cfg.type_alias.is_some()
286 && bridge_cfg.register_fn.is_none()
287 && bridge_cfg.super_trait.is_none()
288 && trait_type.methods.iter().all(|m| m.has_default_impl);
289
290 if is_visitor_bridge {
291 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
292 let trait_path = trait_type.rust_path.replace('-', "_");
293 let code = gen_visitor_bridge(trait_type, bridge_cfg, &struct_name, &trait_path, &type_paths);
294 BridgeOutput { imports: vec![], code }
295 } else {
296 let generator = PhpBridgeGenerator {
298 core_import: core_import.to_string(),
299 type_paths: type_paths.clone(),
300 error_type: error_type.to_string(),
301 };
302 let spec = TraitBridgeSpec {
303 trait_def: trait_type,
304 bridge_config: bridge_cfg,
305 core_import,
306 wrapper_prefix: "Php",
307 type_paths,
308 error_type: error_type.to_string(),
309 };
310 gen_bridge_all(&spec, &generator)
311 }
312}
313
314fn gen_visitor_bridge(
319 trait_type: &TypeDef,
320 _bridge_cfg: &TraitBridgeConfig,
321 struct_name: &str,
322 trait_path: &str,
323 type_paths: &HashMap<String, String>,
324) -> String {
325 let mut out = String::with_capacity(4096);
326 let core_crate = trait_path
327 .split("::")
328 .next()
329 .unwrap_or("html_to_markdown_rs")
330 .to_string();
331 writeln!(out, "fn nodecontext_to_php_array(").unwrap();
333 writeln!(out, " ctx: &{core_crate}::visitor::NodeContext,").unwrap();
334 writeln!(out, ") -> ext_php_rs::boxed::ZBox<ext_php_rs::types::ZendHashTable> {{").unwrap();
335 writeln!(out, " let mut arr = ext_php_rs::types::ZendHashTable::new();").unwrap();
336 writeln!(
337 out,
338 " arr.insert(\"nodeType\", ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", ctx.node_type)).unwrap_or_default()).ok();"
339 )
340 .unwrap();
341 writeln!(
342 out,
343 " arr.insert(\"tagName\", ext_php_rs::types::Zval::try_from(ctx.tag_name.clone()).unwrap_or_default()).ok();"
344 )
345 .unwrap();
346 writeln!(
347 out,
348 " arr.insert(\"depth\", ext_php_rs::types::Zval::try_from(ctx.depth as i64).unwrap_or_default()).ok();"
349 )
350 .unwrap();
351 writeln!(
352 out,
353 " arr.insert(\"indexInParent\", ext_php_rs::types::Zval::try_from(ctx.index_in_parent as i64).unwrap_or_default()).ok();"
354 )
355 .unwrap();
356 writeln!(
357 out,
358 " arr.insert(\"isInline\", ext_php_rs::types::Zval::try_from(ctx.is_inline).unwrap_or_default()).ok();"
359 )
360 .unwrap();
361 writeln!(out, " if let Some(ref pt) = ctx.parent_tag {{").unwrap();
362 writeln!(
363 out,
364 " arr.insert(\"parentTag\", ext_php_rs::types::Zval::try_from(pt.clone()).unwrap_or_default()).ok();"
365 )
366 .unwrap();
367 writeln!(out, " }}").unwrap();
368 writeln!(out, " let mut attrs = ext_php_rs::types::ZendHashTable::new();").unwrap();
369 writeln!(out, " for (k, v) in &ctx.attributes {{").unwrap();
370 writeln!(
371 out,
372 " attrs.insert(k.as_str(), ext_php_rs::types::Zval::try_from(v.clone()).unwrap_or_default()).ok();"
373 )
374 .unwrap();
375 writeln!(out, " }}").unwrap();
376 writeln!(out, " let mut attrs_zval = ext_php_rs::types::Zval::new();").unwrap();
377 writeln!(out, " attrs_zval.set_hashtable(attrs);").unwrap();
378 writeln!(out, " arr.insert(\"attributes\", attrs_zval).ok();").unwrap();
379 writeln!(out, " arr").unwrap();
380 writeln!(out, "}}").unwrap();
381 writeln!(out).unwrap();
382
383 writeln!(out, "pub struct {struct_name} {{").unwrap();
387 writeln!(out, " php_obj: *mut ext_php_rs::types::ZendObject,").unwrap();
388 writeln!(out, " cached_name: String,").unwrap();
389 writeln!(out, "}}").unwrap();
390 writeln!(out).unwrap();
391
392 writeln!(out, "// SAFETY: PHP objects are single-threaded; the bridge is used").unwrap();
395 writeln!(out, "// only within a single PHP request, never across threads.").unwrap();
396 writeln!(out, "unsafe impl Send for {struct_name} {{}}").unwrap();
397 writeln!(out, "unsafe impl Sync for {struct_name} {{}}").unwrap();
398 writeln!(out).unwrap();
399
400 writeln!(out, "impl Clone for {struct_name} {{").unwrap();
401 writeln!(out, " fn clone(&self) -> Self {{").unwrap();
402 writeln!(out, " Self {{").unwrap();
403 writeln!(out, " php_obj: self.php_obj,").unwrap();
404 writeln!(out, " cached_name: self.cached_name.clone(),").unwrap();
405 writeln!(out, " }}").unwrap();
406 writeln!(out, " }}").unwrap();
407 writeln!(out, "}}").unwrap();
408 writeln!(out).unwrap();
409
410 writeln!(out, "impl std::fmt::Debug for {struct_name} {{").unwrap();
412 writeln!(
413 out,
414 " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
415 )
416 .unwrap();
417 writeln!(out, " write!(f, \"{struct_name}\")").unwrap();
418 writeln!(out, " }}").unwrap();
419 writeln!(out, "}}").unwrap();
420 writeln!(out).unwrap();
421
422 writeln!(out, "impl {struct_name} {{").unwrap();
426 writeln!(
427 out,
428 " pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
429 )
430 .unwrap();
431 writeln!(out, " let cached_name = php_obj").unwrap();
432 writeln!(out, " .try_call_method(\"name\", vec![])").unwrap();
433 writeln!(out, " .ok()").unwrap();
434 writeln!(out, " .and_then(|v| v.string())").unwrap();
435 writeln!(out, " .unwrap_or(\"unknown\".into())").unwrap();
436 writeln!(out, " .to_string();").unwrap();
437 writeln!(out, " Self {{ php_obj: php_obj as *mut _, cached_name }}").unwrap();
438 writeln!(out, " }}").unwrap();
439 writeln!(out, "}}").unwrap();
440 writeln!(out).unwrap();
441
442 writeln!(out, "impl {trait_path} for {struct_name} {{").unwrap();
444 for method in &trait_type.methods {
445 if method.trait_source.is_some() {
446 continue;
447 }
448 gen_visitor_method_php(&mut out, method, type_paths);
449 }
450 writeln!(out, "}}").unwrap();
451 writeln!(out).unwrap();
452
453 out
454}
455
456fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
458 if optional && matches!(ty, TypeRef::String) && is_ref {
459 return "Option<&str>".to_string();
460 }
461 if is_ref {
462 if let TypeRef::Vec(inner) = ty {
463 let inner_str = param_type(inner, "", false, tp);
464 return format!("&[{inner_str}]");
465 }
466 }
467 param_type(ty, "", is_ref, tp)
468}
469
470fn gen_visitor_method_php(out: &mut String, method: &MethodDef, type_paths: &HashMap<String, String>) {
472 let name = &method.name;
473 let php_name = to_camel_case(name);
474
475 let mut sig_parts = vec!["&mut self".to_string()];
476 for p in &method.params {
477 let ty_str = visitor_param_type(&p.ty, p.is_ref, p.optional, type_paths);
478 sig_parts.push(format!("{}: {}", p.name, ty_str));
479 }
480 let sig = sig_parts.join(", ");
481
482 let ret_ty = match &method.return_type {
483 TypeRef::Named(n) => type_paths.get(n).cloned().unwrap_or_else(|| n.clone()),
484 other => param_type(other, "", false, type_paths),
485 };
486
487 writeln!(out, " fn {name}({sig}) -> {ret_ty} {{").unwrap();
488
489 writeln!(
491 out,
492 " // SAFETY: php_obj is a valid ZendObject pointer for the duration of this call."
493 )
494 .unwrap();
495 writeln!(out, " let php_obj_ref = unsafe {{ &mut *self.php_obj }};").unwrap();
496
497 let has_args = !method.params.is_empty();
499 if has_args {
500 writeln!(out, " let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").unwrap();
501 for p in &method.params {
502 if let TypeRef::Named(n) = &p.ty {
503 if n == "NodeContext" {
504 writeln!(
505 out,
506 " let ctx_arr = nodecontext_to_php_array({}{});",
507 if p.is_ref { "" } else { "&" },
508 p.name
509 )
510 .unwrap();
511 writeln!(
512 out,
513 " args.push(ext_php_rs::convert::IntoZval::into_zval(ctx_arr, false).unwrap_or_default());"
514 )
515 .unwrap();
516 continue;
517 }
518 }
519 if p.optional && matches!(&p.ty, TypeRef::String) && p.is_ref {
522 writeln!(
523 out,
524 " args.push(match {0} {{ Some(s) => ext_php_rs::types::Zval::try_from(s.to_string()).unwrap_or_default(), None => ext_php_rs::types::Zval::new() }});",
525 p.name
526 )
527 .unwrap();
528 continue;
529 }
530 if matches!(&p.ty, TypeRef::String) {
531 if p.is_ref {
532 writeln!(
533 out,
534 " args.push(ext_php_rs::types::Zval::try_from({}.to_string()).unwrap_or_default());",
535 p.name
536 )
537 .unwrap();
538 } else {
539 writeln!(
540 out,
541 " args.push(ext_php_rs::types::Zval::try_from({}.clone()).unwrap_or_default());",
542 p.name
543 )
544 .unwrap();
545 }
546 continue;
547 }
548 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool)) {
549 writeln!(
550 out,
551 " {{ let mut _zv = ext_php_rs::types::Zval::new(); _zv.set_bool({}); args.push(_zv); }}",
552 p.name
553 )
554 .unwrap();
555 continue;
556 }
557 writeln!(
559 out,
560 " args.push(ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default());",
561 p.name
562 )
563 .unwrap();
564 }
565 }
566
567 if has_args {
571 writeln!(
572 out,
573 " let dyn_args: Vec<&dyn ext_php_rs::convert::IntoZvalDyn> = args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect();"
574 )
575 .unwrap();
576 }
577 let args_expr = if has_args { "dyn_args" } else { "vec![]" };
578 writeln!(
579 out,
580 " let result = php_obj_ref.try_call_method(\"{php_name}\", {args_expr});"
581 )
582 .unwrap();
583
584 writeln!(out, " match result {{").unwrap();
586 writeln!(out, " Err(_) => {ret_ty}::Continue,").unwrap();
587 writeln!(out, " Ok(val) => {{").unwrap();
588 writeln!(
589 out,
590 " let s = val.string().unwrap_or_default().to_lowercase();"
591 )
592 .unwrap();
593 writeln!(out, " match s.as_str() {{").unwrap();
594 writeln!(out, " \"continue\" => {ret_ty}::Continue,").unwrap();
595 writeln!(out, " \"skip\" => {ret_ty}::Skip,").unwrap();
596 writeln!(
597 out,
598 " \"preserve_html\" | \"preservehtml\" => {ret_ty}::PreserveHtml,"
599 )
600 .unwrap();
601 writeln!(out, " other => {ret_ty}::Custom(other.to_string()),").unwrap();
602 writeln!(out, " }}").unwrap();
603 writeln!(out, " }}").unwrap();
604 writeln!(out, " }}").unwrap();
605 writeln!(out, " }}").unwrap();
606 writeln!(out).unwrap();
607}
608
609fn to_camel_case(name: &str) -> String {
611 let mut result = String::with_capacity(name.len());
612 let mut capitalize_next = false;
613 for (i, c) in name.chars().enumerate() {
614 if c == '_' {
615 capitalize_next = true;
616 } else if capitalize_next {
617 result.extend(c.to_uppercase());
618 capitalize_next = false;
619 } else if i == 0 {
620 result.extend(c.to_lowercase());
621 } else {
622 result.push(c);
623 }
624 }
625 result
626}
627
628fn param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
630 match ty {
631 TypeRef::Bytes if is_ref => "&[u8]".into(),
632 TypeRef::Bytes => "Vec<u8>".into(),
633 TypeRef::String if is_ref => "&str".into(),
634 TypeRef::String => "String".into(),
635 TypeRef::Path if is_ref => "&std::path::Path".into(),
636 TypeRef::Path => "std::path::PathBuf".into(),
637 TypeRef::Named(n) => {
638 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
639 if is_ref { format!("&{qualified}") } else { qualified }
640 }
641 TypeRef::Vec(inner) => format!("Vec<{}>", param_type(inner, ci, false, tp)),
642 TypeRef::Optional(inner) => format!("Option<{}>", param_type(inner, ci, false, tp)),
643 TypeRef::Primitive(p) => prim(p).into(),
644 TypeRef::Unit => "()".into(),
645 TypeRef::Char => "char".into(),
646 TypeRef::Map(k, v) => format!(
647 "std::collections::HashMap<{}, {}>",
648 param_type(k, ci, false, tp),
649 param_type(v, ci, false, tp)
650 ),
651 TypeRef::Json => "serde_json::Value".into(),
652 TypeRef::Duration => "std::time::Duration".into(),
653 }
654}
655
656fn prim(p: &alef_core::ir::PrimitiveType) -> &'static str {
657 use alef_core::ir::PrimitiveType::*;
658 match p {
659 Bool => "bool",
660 U8 => "u8",
661 U16 => "u16",
662 U32 => "u32",
663 U64 => "u64",
664 I8 => "i8",
665 I16 => "i16",
666 I32 => "i32",
667 I64 => "i64",
668 F32 => "f32",
669 F64 => "f64",
670 Usize => "usize",
671 Isize => "isize",
672 }
673}
674
675pub fn find_bridge_param<'a>(
680 func: &alef_core::ir::FunctionDef,
681 bridges: &'a [TraitBridgeConfig],
682) -> Option<(usize, &'a TraitBridgeConfig)> {
683 for (idx, param) in func.params.iter().enumerate() {
684 let named = match ¶m.ty {
685 TypeRef::Named(n) => Some(n.as_str()),
686 TypeRef::Optional(inner) => {
687 if let TypeRef::Named(n) = inner.as_ref() {
688 Some(n.as_str())
689 } else {
690 None
691 }
692 }
693 _ => None,
694 };
695 for bridge in bridges {
696 if let Some(type_name) = named {
697 if bridge.type_alias.as_deref() == Some(type_name) {
698 return Some((idx, bridge));
699 }
700 }
701 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
702 return Some((idx, bridge));
703 }
704 }
705 }
706 None
707}
708
709#[allow(clippy::too_many_arguments)]
712pub fn gen_bridge_function(
713 func: &alef_core::ir::FunctionDef,
714 bridge_param_idx: usize,
715 bridge_cfg: &TraitBridgeConfig,
716 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
717 opaque_types: &ahash::AHashSet<String>,
718 core_import: &str,
719) -> String {
720 use alef_core::ir::TypeRef;
721
722 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
723 let handle_path = format!("{core_import}::visitor::VisitorHandle");
724 let param_name = &func.params[bridge_param_idx].name;
725 let bridge_param = &func.params[bridge_param_idx];
726 let is_optional = bridge_param.optional || matches!(&bridge_param.ty, TypeRef::Optional(_));
727
728 let mut sig_parts = Vec::new();
730 for (idx, p) in func.params.iter().enumerate() {
731 if idx == bridge_param_idx {
732 let php_obj_ty = "&mut ext_php_rs::types::ZendObject";
736 if is_optional {
737 sig_parts.push(format!("{}: Option<{php_obj_ty}>", p.name));
738 } else {
739 sig_parts.push(format!("{}: {php_obj_ty}", p.name));
740 }
741 } else {
742 let promoted = idx > bridge_param_idx || func.params[..idx].iter().any(|pp| pp.optional);
743 let base = mapper.map_type(&p.ty);
744 let ty = match &p.ty {
747 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
748 if p.optional || promoted {
749 format!("Option<&mut {base}>")
750 } else {
751 format!("&mut {base}")
752 }
753 }
754 TypeRef::Optional(inner) => {
755 if let TypeRef::Named(n) = inner.as_ref() {
756 if !opaque_types.contains(n.as_str()) {
757 format!("Option<&mut {base}>")
758 } else if p.optional || promoted {
759 format!("Option<{base}>")
760 } else {
761 base
762 }
763 } else if p.optional || promoted {
764 format!("Option<{base}>")
765 } else {
766 base
767 }
768 }
769 _ => {
770 if p.optional || promoted {
771 format!("Option<{base}>")
772 } else {
773 base
774 }
775 }
776 };
777 sig_parts.push(format!("{}: {}", p.name, ty));
778 }
779 }
780
781 let params_str = sig_parts.join(", ");
782 let return_type = mapper.map_type(&func.return_type);
783 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
784
785 let err_conv = ".map_err(|e| ext_php_rs::exception::PhpException::default(e.to_string()))";
786
787 let bridge_wrap = if is_optional {
789 format!(
790 "let {param_name} = {param_name}.map(|v| {{\n \
791 let bridge = {struct_name}::new(v);\n \
792 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
793 }});"
794 )
795 } else {
796 format!(
797 "let {param_name} = {{\n \
798 let bridge = {struct_name}::new({param_name});\n \
799 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
800 }};"
801 )
802 };
803
804 let serde_bindings: String = func
806 .params
807 .iter()
808 .enumerate()
809 .filter(|(idx, p)| {
810 if *idx == bridge_param_idx {
811 return false;
812 }
813 let named = match &p.ty {
814 TypeRef::Named(n) => Some(n.as_str()),
815 TypeRef::Optional(inner) => {
816 if let TypeRef::Named(n) = inner.as_ref() {
817 Some(n.as_str())
818 } else {
819 None
820 }
821 }
822 _ => None,
823 };
824 named.is_some_and(|n| !opaque_types.contains(n))
825 })
826 .map(|(_, p)| {
827 let name = &p.name;
828 let core_path = format!(
829 "{core_import}::{}",
830 match &p.ty {
831 TypeRef::Named(n) => n.clone(),
832 TypeRef::Optional(inner) =>
833 if let TypeRef::Named(n) = inner.as_ref() {
834 n.clone()
835 } else {
836 String::new()
837 },
838 _ => String::new(),
839 }
840 );
841 if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
842 format!(
843 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n \
844 let json = serde_json::to_string(&v){err_conv}?;\n \
845 serde_json::from_str(&json){err_conv}\n \
846 }}).transpose()?;\n "
847 )
848 } else {
849 format!(
850 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n \
851 let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n "
852 )
853 }
854 })
855 .collect();
856
857 let call_args: Vec<String> = func
859 .params
860 .iter()
861 .enumerate()
862 .map(|(idx, p)| {
863 if idx == bridge_param_idx {
864 return p.name.clone();
865 }
866 match &p.ty {
867 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
868 if p.optional {
869 format!("{}.as_ref().map(|v| &v.inner)", p.name)
870 } else {
871 format!("&{}.inner", p.name)
872 }
873 }
874 TypeRef::Named(_) => format!("{}_core", p.name),
875 TypeRef::Optional(inner) => {
876 if let TypeRef::Named(n) = inner.as_ref() {
877 if opaque_types.contains(n.as_str()) {
878 format!("{}.as_ref().map(|v| &v.inner)", p.name)
879 } else {
880 format!("{}_core", p.name)
881 }
882 } else {
883 p.name.clone()
884 }
885 }
886 TypeRef::String | TypeRef::Char => {
887 if p.is_ref {
888 format!("&{}", p.name)
889 } else {
890 p.name.clone()
891 }
892 }
893 _ => p.name.clone(),
894 }
895 })
896 .collect();
897 let call_args_str = call_args.join(", ");
898
899 let core_fn_path = {
900 let path = func.rust_path.replace('-', "_");
901 if path.starts_with(core_import) {
902 path
903 } else {
904 format!("{core_import}::{}", func.name)
905 }
906 };
907 let core_call = format!("{core_fn_path}({call_args_str})");
908
909 let return_wrap = match &func.return_type {
910 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
911 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
912 }
913 TypeRef::Named(_) => "val.into()".to_string(),
914 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
915 _ => "val".to_string(),
916 };
917
918 let body = if func.error_type.is_some() {
919 if return_wrap == "val" {
920 format!("{bridge_wrap}\n {serde_bindings}{core_call}{err_conv}")
921 } else {
922 format!("{bridge_wrap}\n {serde_bindings}{core_call}.map(|val| {return_wrap}){err_conv}")
923 }
924 } else {
925 format!("{bridge_wrap}\n {serde_bindings}{core_call}")
926 };
927
928 let func_name = &func.name;
929 let mut out = String::with_capacity(1024);
930 if func.error_type.is_some() {
931 writeln!(out, "#[allow(clippy::missing_errors_doc)]").ok();
932 }
933 writeln!(out, "pub fn {func_name}({params_str}) -> {ret} {{").ok();
934 writeln!(out, " {body}").ok();
935 writeln!(out, "}}").ok();
936
937 out
938}