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