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