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 error_constructor: &str,
271 api: &ApiSurface,
272) -> BridgeOutput {
273 let type_paths: HashMap<String, String> = api
275 .types
276 .iter()
277 .map(|t| (t.name.clone(), t.rust_path.replace('-', "_")))
278 .chain(
279 api.enums
280 .iter()
281 .map(|e| (e.name.clone(), e.rust_path.replace('-', "_"))),
282 )
283 .collect();
284
285 let is_visitor_bridge = bridge_cfg.type_alias.is_some()
287 && bridge_cfg.register_fn.is_none()
288 && bridge_cfg.super_trait.is_none()
289 && trait_type.methods.iter().all(|m| m.has_default_impl);
290
291 if is_visitor_bridge {
292 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
293 let trait_path = trait_type.rust_path.replace('-', "_");
294 let code = gen_visitor_bridge(trait_type, bridge_cfg, &struct_name, &trait_path, &type_paths);
295 BridgeOutput { imports: vec![], code }
296 } else {
297 let generator = PhpBridgeGenerator {
299 core_import: core_import.to_string(),
300 type_paths: type_paths.clone(),
301 error_type: error_type.to_string(),
302 };
303 let spec = TraitBridgeSpec {
304 trait_def: trait_type,
305 bridge_config: bridge_cfg,
306 core_import,
307 wrapper_prefix: "Php",
308 type_paths,
309 error_type: error_type.to_string(),
310 error_constructor: error_constructor.to_string(),
311 };
312 gen_bridge_all(&spec, &generator)
313 }
314}
315
316fn gen_visitor_bridge(
321 trait_type: &TypeDef,
322 _bridge_cfg: &TraitBridgeConfig,
323 struct_name: &str,
324 trait_path: &str,
325 type_paths: &HashMap<String, String>,
326) -> String {
327 let mut out = String::with_capacity(4096);
328 let core_crate = trait_path
329 .split("::")
330 .next()
331 .unwrap_or("html_to_markdown_rs")
332 .to_string();
333 writeln!(out, "fn nodecontext_to_php_array(").unwrap();
335 writeln!(out, " ctx: &{core_crate}::visitor::NodeContext,").unwrap();
336 writeln!(out, ") -> ext_php_rs::boxed::ZBox<ext_php_rs::types::ZendHashTable> {{").unwrap();
337 writeln!(out, " let mut arr = ext_php_rs::types::ZendHashTable::new();").unwrap();
338 writeln!(
339 out,
340 " arr.insert(\"nodeType\", ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", ctx.node_type)).unwrap_or_default()).ok();"
341 )
342 .unwrap();
343 writeln!(
344 out,
345 " arr.insert(\"tagName\", ext_php_rs::types::Zval::try_from(ctx.tag_name.clone()).unwrap_or_default()).ok();"
346 )
347 .unwrap();
348 writeln!(
349 out,
350 " arr.insert(\"depth\", ext_php_rs::types::Zval::try_from(ctx.depth as i64).unwrap_or_default()).ok();"
351 )
352 .unwrap();
353 writeln!(
354 out,
355 " arr.insert(\"indexInParent\", ext_php_rs::types::Zval::try_from(ctx.index_in_parent as i64).unwrap_or_default()).ok();"
356 )
357 .unwrap();
358 writeln!(
359 out,
360 " arr.insert(\"isInline\", ext_php_rs::types::Zval::try_from(ctx.is_inline).unwrap_or_default()).ok();"
361 )
362 .unwrap();
363 writeln!(out, " if let Some(ref pt) = ctx.parent_tag {{").unwrap();
364 writeln!(
365 out,
366 " arr.insert(\"parentTag\", ext_php_rs::types::Zval::try_from(pt.clone()).unwrap_or_default()).ok();"
367 )
368 .unwrap();
369 writeln!(out, " }}").unwrap();
370 writeln!(out, " let mut attrs = ext_php_rs::types::ZendHashTable::new();").unwrap();
371 writeln!(out, " for (k, v) in &ctx.attributes {{").unwrap();
372 writeln!(
373 out,
374 " attrs.insert(k.as_str(), ext_php_rs::types::Zval::try_from(v.clone()).unwrap_or_default()).ok();"
375 )
376 .unwrap();
377 writeln!(out, " }}").unwrap();
378 writeln!(out, " let mut attrs_zval = ext_php_rs::types::Zval::new();").unwrap();
379 writeln!(out, " attrs_zval.set_hashtable(attrs);").unwrap();
380 writeln!(out, " arr.insert(\"attributes\", attrs_zval).ok();").unwrap();
381 writeln!(out, " arr").unwrap();
382 writeln!(out, "}}").unwrap();
383 writeln!(out).unwrap();
384
385 writeln!(out, "pub struct {struct_name} {{").unwrap();
389 writeln!(out, " php_obj: *mut ext_php_rs::types::ZendObject,").unwrap();
390 writeln!(out, " cached_name: String,").unwrap();
391 writeln!(out, "}}").unwrap();
392 writeln!(out).unwrap();
393
394 writeln!(out, "// SAFETY: PHP objects are single-threaded; the bridge is used").unwrap();
397 writeln!(out, "// only within a single PHP request, never across threads.").unwrap();
398 writeln!(out, "unsafe impl Send for {struct_name} {{}}").unwrap();
399 writeln!(out, "unsafe impl Sync for {struct_name} {{}}").unwrap();
400 writeln!(out).unwrap();
401
402 writeln!(out, "impl Clone for {struct_name} {{").unwrap();
403 writeln!(out, " fn clone(&self) -> Self {{").unwrap();
404 writeln!(out, " Self {{").unwrap();
405 writeln!(out, " php_obj: self.php_obj,").unwrap();
406 writeln!(out, " cached_name: self.cached_name.clone(),").unwrap();
407 writeln!(out, " }}").unwrap();
408 writeln!(out, " }}").unwrap();
409 writeln!(out, "}}").unwrap();
410 writeln!(out).unwrap();
411
412 writeln!(out, "impl std::fmt::Debug for {struct_name} {{").unwrap();
414 writeln!(
415 out,
416 " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
417 )
418 .unwrap();
419 writeln!(out, " write!(f, \"{struct_name}\")").unwrap();
420 writeln!(out, " }}").unwrap();
421 writeln!(out, "}}").unwrap();
422 writeln!(out).unwrap();
423
424 writeln!(out, "impl {struct_name} {{").unwrap();
428 writeln!(
429 out,
430 " pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
431 )
432 .unwrap();
433 writeln!(out, " let cached_name = php_obj").unwrap();
434 writeln!(out, " .try_call_method(\"name\", vec![])").unwrap();
435 writeln!(out, " .ok()").unwrap();
436 writeln!(out, " .and_then(|v| v.string())").unwrap();
437 writeln!(out, " .unwrap_or(\"unknown\".into())").unwrap();
438 writeln!(out, " .to_string();").unwrap();
439 writeln!(out, " Self {{ php_obj: php_obj as *mut _, cached_name }}").unwrap();
440 writeln!(out, " }}").unwrap();
441 writeln!(out, "}}").unwrap();
442 writeln!(out).unwrap();
443
444 writeln!(out, "impl {trait_path} for {struct_name} {{").unwrap();
446 for method in &trait_type.methods {
447 if method.trait_source.is_some() {
448 continue;
449 }
450 gen_visitor_method_php(&mut out, method, type_paths);
451 }
452 writeln!(out, "}}").unwrap();
453 writeln!(out).unwrap();
454
455 out
456}
457
458fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
460 if optional && matches!(ty, TypeRef::String) && is_ref {
461 return "Option<&str>".to_string();
462 }
463 if is_ref {
464 if let TypeRef::Vec(inner) = ty {
465 let inner_str = param_type(inner, "", false, tp);
466 return format!("&[{inner_str}]");
467 }
468 }
469 param_type(ty, "", is_ref, tp)
470}
471
472fn gen_visitor_method_php(out: &mut String, method: &MethodDef, type_paths: &HashMap<String, String>) {
474 let name = &method.name;
475 let php_name = to_camel_case(name);
476
477 let mut sig_parts = vec!["&mut self".to_string()];
478 for p in &method.params {
479 let ty_str = visitor_param_type(&p.ty, p.is_ref, p.optional, type_paths);
480 sig_parts.push(format!("{}: {}", p.name, ty_str));
481 }
482 let sig = sig_parts.join(", ");
483
484 let ret_ty = match &method.return_type {
485 TypeRef::Named(n) => type_paths.get(n).cloned().unwrap_or_else(|| n.clone()),
486 other => param_type(other, "", false, type_paths),
487 };
488
489 writeln!(out, " fn {name}({sig}) -> {ret_ty} {{").unwrap();
490
491 writeln!(
493 out,
494 " // SAFETY: php_obj is a valid ZendObject pointer for the duration of this call."
495 )
496 .unwrap();
497 writeln!(out, " let php_obj_ref = unsafe {{ &mut *self.php_obj }};").unwrap();
498
499 let has_args = !method.params.is_empty();
501 if has_args {
502 writeln!(out, " let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").unwrap();
503 for p in &method.params {
504 if let TypeRef::Named(n) = &p.ty {
505 if n == "NodeContext" {
506 writeln!(
507 out,
508 " let ctx_arr = nodecontext_to_php_array({}{});",
509 if p.is_ref { "" } else { "&" },
510 p.name
511 )
512 .unwrap();
513 writeln!(
514 out,
515 " args.push(ext_php_rs::convert::IntoZval::into_zval(ctx_arr, false).unwrap_or_default());"
516 )
517 .unwrap();
518 continue;
519 }
520 }
521 if p.optional && matches!(&p.ty, TypeRef::String) && p.is_ref {
524 writeln!(
525 out,
526 " 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() }});",
527 p.name
528 )
529 .unwrap();
530 continue;
531 }
532 if matches!(&p.ty, TypeRef::String) {
533 if p.is_ref {
534 writeln!(
535 out,
536 " args.push(ext_php_rs::types::Zval::try_from({}.to_string()).unwrap_or_default());",
537 p.name
538 )
539 .unwrap();
540 } else {
541 writeln!(
542 out,
543 " args.push(ext_php_rs::types::Zval::try_from({}.clone()).unwrap_or_default());",
544 p.name
545 )
546 .unwrap();
547 }
548 continue;
549 }
550 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool)) {
551 writeln!(
552 out,
553 " {{ let mut _zv = ext_php_rs::types::Zval::new(); _zv.set_bool({}); args.push(_zv); }}",
554 p.name
555 )
556 .unwrap();
557 continue;
558 }
559 writeln!(
561 out,
562 " args.push(ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default());",
563 p.name
564 )
565 .unwrap();
566 }
567 }
568
569 if has_args {
573 writeln!(
574 out,
575 " let dyn_args: Vec<&dyn ext_php_rs::convert::IntoZvalDyn> = args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect();"
576 )
577 .unwrap();
578 }
579 let args_expr = if has_args { "dyn_args" } else { "vec![]" };
580 writeln!(
581 out,
582 " let result = php_obj_ref.try_call_method(\"{php_name}\", {args_expr});"
583 )
584 .unwrap();
585
586 writeln!(out, " match result {{").unwrap();
588 writeln!(out, " Err(_) => {ret_ty}::Continue,").unwrap();
589 writeln!(out, " Ok(val) => {{").unwrap();
590 writeln!(
591 out,
592 " let s = val.string().unwrap_or_default().to_lowercase();"
593 )
594 .unwrap();
595 writeln!(out, " match s.as_str() {{").unwrap();
596 writeln!(out, " \"continue\" => {ret_ty}::Continue,").unwrap();
597 writeln!(out, " \"skip\" => {ret_ty}::Skip,").unwrap();
598 writeln!(
599 out,
600 " \"preserve_html\" | \"preservehtml\" => {ret_ty}::PreserveHtml,"
601 )
602 .unwrap();
603 writeln!(out, " other => {ret_ty}::Custom(other.to_string()),").unwrap();
604 writeln!(out, " }}").unwrap();
605 writeln!(out, " }}").unwrap();
606 writeln!(out, " }}").unwrap();
607 writeln!(out, " }}").unwrap();
608 writeln!(out).unwrap();
609}
610
611fn to_camel_case(name: &str) -> String {
613 let mut result = String::with_capacity(name.len());
614 let mut capitalize_next = false;
615 for (i, c) in name.chars().enumerate() {
616 if c == '_' {
617 capitalize_next = true;
618 } else if capitalize_next {
619 result.extend(c.to_uppercase());
620 capitalize_next = false;
621 } else if i == 0 {
622 result.extend(c.to_lowercase());
623 } else {
624 result.push(c);
625 }
626 }
627 result
628}
629
630fn param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
632 match ty {
633 TypeRef::Bytes if is_ref => "&[u8]".into(),
634 TypeRef::Bytes => "Vec<u8>".into(),
635 TypeRef::String if is_ref => "&str".into(),
636 TypeRef::String => "String".into(),
637 TypeRef::Path if is_ref => "&std::path::Path".into(),
638 TypeRef::Path => "std::path::PathBuf".into(),
639 TypeRef::Named(n) => {
640 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
641 if is_ref { format!("&{qualified}") } else { qualified }
642 }
643 TypeRef::Vec(inner) => format!("Vec<{}>", param_type(inner, ci, false, tp)),
644 TypeRef::Optional(inner) => format!("Option<{}>", param_type(inner, ci, false, tp)),
645 TypeRef::Primitive(p) => prim(p).into(),
646 TypeRef::Unit => "()".into(),
647 TypeRef::Char => "char".into(),
648 TypeRef::Map(k, v) => format!(
649 "std::collections::HashMap<{}, {}>",
650 param_type(k, ci, false, tp),
651 param_type(v, ci, false, tp)
652 ),
653 TypeRef::Json => "serde_json::Value".into(),
654 TypeRef::Duration => "std::time::Duration".into(),
655 }
656}
657
658fn prim(p: &alef_core::ir::PrimitiveType) -> &'static str {
659 use alef_core::ir::PrimitiveType::*;
660 match p {
661 Bool => "bool",
662 U8 => "u8",
663 U16 => "u16",
664 U32 => "u32",
665 U64 => "u64",
666 I8 => "i8",
667 I16 => "i16",
668 I32 => "i32",
669 I64 => "i64",
670 F32 => "f32",
671 F64 => "f64",
672 Usize => "usize",
673 Isize => "isize",
674 }
675}
676
677pub fn find_bridge_param<'a>(
682 func: &alef_core::ir::FunctionDef,
683 bridges: &'a [TraitBridgeConfig],
684) -> Option<(usize, &'a TraitBridgeConfig)> {
685 for (idx, param) in func.params.iter().enumerate() {
686 let named = match ¶m.ty {
687 TypeRef::Named(n) => Some(n.as_str()),
688 TypeRef::Optional(inner) => {
689 if let TypeRef::Named(n) = inner.as_ref() {
690 Some(n.as_str())
691 } else {
692 None
693 }
694 }
695 _ => None,
696 };
697 for bridge in bridges {
698 if let Some(type_name) = named {
699 if bridge.type_alias.as_deref() == Some(type_name) {
700 return Some((idx, bridge));
701 }
702 }
703 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
704 return Some((idx, bridge));
705 }
706 }
707 }
708 None
709}
710
711#[allow(clippy::too_many_arguments)]
714pub fn gen_bridge_function(
715 func: &alef_core::ir::FunctionDef,
716 bridge_param_idx: usize,
717 bridge_cfg: &TraitBridgeConfig,
718 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
719 opaque_types: &ahash::AHashSet<String>,
720 core_import: &str,
721) -> String {
722 use alef_core::ir::TypeRef;
723
724 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
725 let handle_path = format!("{core_import}::visitor::VisitorHandle");
726 let param_name = &func.params[bridge_param_idx].name;
727 let bridge_param = &func.params[bridge_param_idx];
728 let is_optional = bridge_param.optional || matches!(&bridge_param.ty, TypeRef::Optional(_));
729
730 let mut sig_parts = Vec::new();
732 for (idx, p) in func.params.iter().enumerate() {
733 if idx == bridge_param_idx {
734 let php_obj_ty = "&mut ext_php_rs::types::ZendObject";
738 if is_optional {
739 sig_parts.push(format!("{}: Option<{php_obj_ty}>", p.name));
740 } else {
741 sig_parts.push(format!("{}: {php_obj_ty}", p.name));
742 }
743 } else {
744 let promoted = idx > bridge_param_idx || func.params[..idx].iter().any(|pp| pp.optional);
745 let base = mapper.map_type(&p.ty);
746 let ty = match &p.ty {
749 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
750 if p.optional || promoted {
751 format!("Option<&mut {base}>")
752 } else {
753 format!("&mut {base}")
754 }
755 }
756 TypeRef::Optional(inner) => {
757 if let TypeRef::Named(n) = inner.as_ref() {
758 if !opaque_types.contains(n.as_str()) {
759 format!("Option<&mut {base}>")
760 } else if p.optional || promoted {
761 format!("Option<{base}>")
762 } else {
763 base
764 }
765 } else if p.optional || promoted {
766 format!("Option<{base}>")
767 } else {
768 base
769 }
770 }
771 _ => {
772 if p.optional || promoted {
773 format!("Option<{base}>")
774 } else {
775 base
776 }
777 }
778 };
779 sig_parts.push(format!("{}: {}", p.name, ty));
780 }
781 }
782
783 let params_str = sig_parts.join(", ");
784 let return_type = mapper.map_type(&func.return_type);
785 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
786
787 let err_conv = ".map_err(|e| ext_php_rs::exception::PhpException::default(e.to_string()))";
788
789 let bridge_wrap = if is_optional {
791 format!(
792 "let {param_name} = {param_name}.map(|v| {{\n \
793 let bridge = {struct_name}::new(v);\n \
794 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
795 }});"
796 )
797 } else {
798 format!(
799 "let {param_name} = {{\n \
800 let bridge = {struct_name}::new({param_name});\n \
801 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
802 }};"
803 )
804 };
805
806 let serde_bindings: String = func
808 .params
809 .iter()
810 .enumerate()
811 .filter(|(idx, p)| {
812 if *idx == bridge_param_idx {
813 return false;
814 }
815 let named = match &p.ty {
816 TypeRef::Named(n) => Some(n.as_str()),
817 TypeRef::Optional(inner) => {
818 if let TypeRef::Named(n) = inner.as_ref() {
819 Some(n.as_str())
820 } else {
821 None
822 }
823 }
824 _ => None,
825 };
826 named.is_some_and(|n| !opaque_types.contains(n))
827 })
828 .map(|(_, p)| {
829 let name = &p.name;
830 let core_path = format!(
831 "{core_import}::{}",
832 match &p.ty {
833 TypeRef::Named(n) => n.clone(),
834 TypeRef::Optional(inner) =>
835 if let TypeRef::Named(n) = inner.as_ref() {
836 n.clone()
837 } else {
838 String::new()
839 },
840 _ => String::new(),
841 }
842 );
843 if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
844 format!(
845 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n \
846 let json = serde_json::to_string(&v){err_conv}?;\n \
847 serde_json::from_str(&json){err_conv}\n \
848 }}).transpose()?;\n "
849 )
850 } else {
851 format!(
852 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n \
853 let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n "
854 )
855 }
856 })
857 .collect();
858
859 let call_args: Vec<String> = func
861 .params
862 .iter()
863 .enumerate()
864 .map(|(idx, p)| {
865 if idx == bridge_param_idx {
866 return p.name.clone();
867 }
868 match &p.ty {
869 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
870 if p.optional {
871 format!("{}.as_ref().map(|v| &v.inner)", p.name)
872 } else {
873 format!("&{}.inner", p.name)
874 }
875 }
876 TypeRef::Named(_) => format!("{}_core", p.name),
877 TypeRef::Optional(inner) => {
878 if let TypeRef::Named(n) = inner.as_ref() {
879 if opaque_types.contains(n.as_str()) {
880 format!("{}.as_ref().map(|v| &v.inner)", p.name)
881 } else {
882 format!("{}_core", p.name)
883 }
884 } else {
885 p.name.clone()
886 }
887 }
888 TypeRef::String | TypeRef::Char => {
889 if p.is_ref {
890 format!("&{}", p.name)
891 } else {
892 p.name.clone()
893 }
894 }
895 _ => p.name.clone(),
896 }
897 })
898 .collect();
899 let call_args_str = call_args.join(", ");
900
901 let core_fn_path = {
902 let path = func.rust_path.replace('-', "_");
903 if path.starts_with(core_import) {
904 path
905 } else {
906 format!("{core_import}::{}", func.name)
907 }
908 };
909 let core_call = format!("{core_fn_path}({call_args_str})");
910
911 let return_wrap = match &func.return_type {
912 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
913 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
914 }
915 TypeRef::Named(_) => "val.into()".to_string(),
916 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
917 _ => "val".to_string(),
918 };
919
920 let body = if func.error_type.is_some() {
921 if return_wrap == "val" {
922 format!("{bridge_wrap}\n {serde_bindings}{core_call}{err_conv}")
923 } else {
924 format!("{bridge_wrap}\n {serde_bindings}{core_call}.map(|val| {return_wrap}){err_conv}")
925 }
926 } else {
927 format!("{bridge_wrap}\n {serde_bindings}{core_call}")
928 };
929
930 let func_name = &func.name;
931 let mut out = String::with_capacity(1024);
932 if func.error_type.is_some() {
933 writeln!(out, "#[allow(clippy::missing_errors_doc)]").ok();
934 }
935 writeln!(out, "pub fn {func_name}({params_str}) -> {ret} {{").ok();
936 writeln!(out, " {body}").ok();
937 writeln!(out, "}}").ok();
938
939 out
940}