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 use alef_codegen::generators::trait_bridge::find_bridge_field;
25
26pub use alef_codegen::generators::trait_bridge::BridgeFieldMatch;
28
29pub struct PhpBridgeGenerator {
32 pub core_import: String,
34 pub type_paths: HashMap<String, String>,
36 pub error_type: String,
38}
39
40impl TraitBridgeGenerator for PhpBridgeGenerator {
41 fn foreign_object_type(&self) -> &str {
42 "*mut ext_php_rs::types::ZendObject"
43 }
44
45 fn bridge_imports(&self) -> Vec<String> {
46 vec!["std::sync::Arc".to_string()]
47 }
48
49 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
50 let name = &method.name;
51 let mut out = String::with_capacity(512);
52
53 writeln!(
55 out,
56 "// SAFETY: PHP objects are single-threaded; method calls are safe within a request."
57 )
58 .ok();
59
60 let has_args = !method.params.is_empty();
61 let args_expr = if has_args {
62 let mut args_parts = Vec::new();
63 for p in &method.params {
64 let arg_expr = match &p.ty {
65 TypeRef::String => format!("ext_php_rs::types::Zval::try_from({}).unwrap_or_default()", p.name),
66 TypeRef::Path => format!(
67 "ext_php_rs::types::Zval::try_from({}.to_string_lossy().to_string()).unwrap_or_default()",
68 p.name
69 ),
70 TypeRef::Bytes => format!(
71 "ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default()",
72 p.name
73 ),
74 TypeRef::Named(_) => {
75 format!(
76 "ext_php_rs::types::Zval::try_from(serde_json::to_string(&{}).unwrap_or_default()).unwrap_or_default()",
77 p.name
78 )
79 }
80 TypeRef::Primitive(_) => {
81 format!("ext_php_rs::types::Zval::try_from({}).unwrap_or_default()", p.name)
82 }
83 _ => format!(
84 "ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default()",
85 p.name
86 ),
87 };
88 args_parts.push(arg_expr);
89 }
90 let args_array = format!("[{}]", args_parts.join(", "));
91 format!(
92 "{}.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect()",
93 args_array
94 )
95 } else {
96 "vec![]".to_string()
97 };
98
99 writeln!(
100 out,
101 "let result = unsafe {{ (*self.inner).try_call_method(\"{name}\", {args_expr}) }};"
102 )
103 .ok();
104
105 if method.error_type.is_some() {
107 writeln!(out, "match result {{").ok();
108 if matches!(method.return_type, TypeRef::Unit) {
110 writeln!(out, " Ok(_) => Ok(()),").ok();
111 } else {
112 writeln!(out, " Ok(val) => {{").ok();
114 writeln!(out, " let json_str = val.string().unwrap_or_default();").ok();
115 writeln!(out, " serde_json::from_str(&json_str).map_err(|e| {}::KreuzbergError::Other(format!(\"Deserialize error: {{}}\", e)))", self.core_import).ok();
116 writeln!(out, " }}").ok();
117 }
118 writeln!(
121 out,
122 " Err(e) => Err({}::KreuzbergError::Other(e.to_string())),",
123 self.core_import
124 )
125 .ok();
126 writeln!(out, "}}").ok();
127 } else {
128 writeln!(out, "match result {{").ok();
129 if matches!(method.return_type, TypeRef::Unit) {
131 writeln!(out, " Ok(_) => (),").ok();
132 } else {
133 writeln!(out, " Ok(val) => {{").ok();
136 writeln!(out, " let json_str = val.string().unwrap_or_default();").ok();
137 writeln!(out, " serde_json::from_str(&json_str).unwrap_or_default()").ok();
138 writeln!(out, " }}").ok();
139 }
140 writeln!(out, " Err(_) => Default::default(),").ok();
141 writeln!(out, "}}").ok();
142 }
143
144 out
145 }
146
147 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
148 let name = &method.name;
149 let mut out = String::with_capacity(1024);
150
151 writeln!(out, "let inner_obj = self.inner;").ok();
152 writeln!(out, "let cached_name = self.cached_name.clone();").ok();
153
154 for p in &method.params {
156 if matches!(&p.ty, TypeRef::String) {
157 writeln!(out, "let {} = {}.clone();", p.name, p.name).ok();
158 }
159 }
160
161 writeln!(out).ok();
162 writeln!(out, "// SAFETY: PHP objects are single-threaded within a request.").ok();
163 writeln!(out, "// The block_on executes within the async runtime.").ok();
164 writeln!(out, "WORKER_RUNTIME.block_on(async {{").ok();
165
166 let has_args = !method.params.is_empty();
167 let args_expr = if has_args {
168 let mut args_parts = Vec::new();
169 for p in &method.params {
170 let arg_expr = match &p.ty {
171 TypeRef::String => format!("ext_php_rs::types::Zval::try_from({}).unwrap_or_default()", p.name),
172 TypeRef::Path => format!(
173 "ext_php_rs::types::Zval::try_from({}.to_string_lossy().to_string()).unwrap_or_default()",
174 p.name
175 ),
176 TypeRef::Bytes => format!(
177 "ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default()",
178 p.name
179 ),
180 TypeRef::Named(_) => {
181 format!(
182 "ext_php_rs::types::Zval::try_from(serde_json::to_string(&{}).unwrap_or_default()).unwrap_or_default()",
183 p.name
184 )
185 }
186 TypeRef::Primitive(_) => {
187 format!("ext_php_rs::types::Zval::try_from({}).unwrap_or_default()", p.name)
188 }
189 _ => format!(
190 "ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default()",
191 p.name
192 ),
193 };
194 args_parts.push(arg_expr);
195 }
196 let args_array = format!("[{}]", args_parts.join(", "));
197 format!(
198 "{}.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect()",
199 args_array
200 )
201 } else {
202 "vec![]".to_string()
203 };
204
205 writeln!(
208 out,
209 " match unsafe {{ (*inner_obj).try_call_method(\"{name}\", {args_expr}) }} {{"
210 )
211 .ok();
212
213 if method.error_type.is_some() {
215 writeln!(out, " Ok(val) => {{").ok();
216 writeln!(out, " let json_str = val.string().unwrap_or_default();").ok();
217 writeln!(
218 out,
219 " serde_json::from_str(&json_str).map_err(|e| {}::KreuzbergError::Other(format!(\"Deserialize error: {{}}\", e)))",
220 spec.core_import
221 )
222 .ok();
223 writeln!(out, " }}").ok();
224 } else {
225 writeln!(
226 out,
227 " Ok(val) => val.string().unwrap_or_default().parse().unwrap_or_default(),"
228 )
229 .ok();
230 }
231
232 writeln!(
233 out,
234 " Err(e) => Err({}::KreuzbergError::Plugin {{",
235 spec.core_import
236 )
237 .ok();
238 writeln!(
239 out,
240 " message: format!(\"Plugin '{{}}' method '{name}' failed: {{}}\", cached_name, e),"
241 )
242 .ok();
243 writeln!(out, " plugin_name: cached_name.clone(),").ok();
244 writeln!(out, " }}),").ok();
245 writeln!(out, " }}").ok();
246 writeln!(out, "}})",).ok();
247
248 out
249 }
250
251 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
252 let wrapper = spec.wrapper_name();
253 let mut out = String::with_capacity(512);
254
255 writeln!(out, "impl {wrapper} {{").ok();
256 writeln!(out, " /// Create a new bridge wrapping a PHP object.").ok();
257 writeln!(out, " ///").ok();
258 writeln!(
259 out,
260 " /// Validates that the PHP object provides all required methods."
261 )
262 .ok();
263 writeln!(
264 out,
265 " pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
266 )
267 .ok();
268
269 writeln!(out, " let cached_name = php_obj").ok();
274 writeln!(out, " .try_call_method(\"name\", vec![])").ok();
275 writeln!(out, " .ok()").ok();
276 writeln!(out, " .and_then(|v| v.string())").ok();
277 writeln!(out, " .unwrap_or(\"unknown\".into())").ok();
278 writeln!(out, " .to_string();").ok();
279
280 writeln!(out).ok();
281 writeln!(out, " Self {{").ok();
282 writeln!(out, " inner: php_obj as *mut _,").ok();
283 writeln!(out, " cached_name,").ok();
284 writeln!(out, " }}").ok();
285 writeln!(out, " }}").ok();
286 writeln!(out, "}}").ok();
287
288 writeln!(out).ok();
292 writeln!(out, "// SAFETY: PHP is single-threaded within a request context.").ok();
293 writeln!(
294 out,
295 "// The raw pointer to ZendObject is only used within a single PHP request"
296 )
297 .ok();
298 writeln!(out, "// and is never accessed concurrently from multiple threads.").ok();
299 writeln!(out, "unsafe impl Send for {wrapper} {{}}").ok();
300 writeln!(out, "unsafe impl Sync for {wrapper} {{}}").ok();
301
302 out
303 }
304
305 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
306 let Some(register_fn) = spec.bridge_config.register_fn.as_deref() else {
307 return String::new();
308 };
309 let Some(registry_getter) = spec.bridge_config.registry_getter.as_deref() else {
310 return String::new();
311 };
312 let wrapper = spec.wrapper_name();
313 let trait_path = spec.trait_path();
314
315 let mut out = String::with_capacity(1024);
316
317 writeln!(out, "#[php_function]").ok();
318 writeln!(
319 out,
320 "pub fn {register_fn}(backend: &mut ext_php_rs::types::ZendObject) -> ext_php_rs::prelude::PhpResult<()> {{"
321 )
322 .ok();
323
324 let req_methods: Vec<&MethodDef> = spec.required_methods();
326 if !req_methods.is_empty() {
327 for method in &req_methods {
328 writeln!(
330 out,
331 r#" if backend.try_call_method("{}", vec![]).is_err() {{"#,
332 method.name
333 )
334 .ok();
335 writeln!(out, " return Err(ext_php_rs::exception::PhpException::default(").ok();
336 writeln!(
337 out,
338 " format!(\"Backend missing required method: {{}}\", \"{}\")",
339 method.name
340 )
341 .ok();
342 writeln!(out, " ));").ok();
343 writeln!(out, " }}").ok();
344 }
345 }
346
347 writeln!(out).ok();
348 writeln!(out, " let wrapper = {wrapper}::new(backend);").ok();
349 writeln!(
350 out,
351 " let arc: std::sync::Arc<dyn {trait_path}> = std::sync::Arc::new(wrapper);"
352 )
353 .ok();
354 writeln!(out).ok();
355
356 let extra = spec
357 .bridge_config
358 .register_extra_args
359 .as_deref()
360 .map(|a| format!(", {a}"))
361 .unwrap_or_default();
362 writeln!(out, " let registry = {registry_getter}();").ok();
363 writeln!(out, " let mut registry = registry.write();").ok();
364 writeln!(
365 out,
366 " registry.register(arc{extra}).map_err(|e| ext_php_rs::exception::PhpException::default("
367 )
368 .ok();
369 writeln!(out, " format!(\"Failed to register backend: {{}}\", e)").ok();
370 writeln!(out, " ))?;").ok();
371 writeln!(out, " Ok(())").ok();
372 writeln!(out, "}}").ok();
373
374 out
375 }
376}
377
378pub fn gen_trait_bridge(
380 trait_type: &TypeDef,
381 bridge_cfg: &TraitBridgeConfig,
382 core_import: &str,
383 error_type: &str,
384 error_constructor: &str,
385 api: &ApiSurface,
386) -> BridgeOutput {
387 let type_paths: HashMap<String, String> = api
389 .types
390 .iter()
391 .map(|t| (t.name.clone(), t.rust_path.replace('-', "_")))
392 .chain(
393 api.enums
394 .iter()
395 .map(|e| (e.name.clone(), e.rust_path.replace('-', "_"))),
396 )
397 .collect();
398
399 let is_visitor_bridge = bridge_cfg.type_alias.is_some()
401 && bridge_cfg.register_fn.is_none()
402 && bridge_cfg.super_trait.is_none()
403 && trait_type.methods.iter().all(|m| m.has_default_impl);
404
405 if is_visitor_bridge {
406 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
407 let trait_path = trait_type.rust_path.replace('-', "_");
408 let code = gen_visitor_bridge(trait_type, bridge_cfg, &struct_name, &trait_path, &type_paths);
409 BridgeOutput { imports: vec![], code }
410 } else {
411 let generator = PhpBridgeGenerator {
413 core_import: core_import.to_string(),
414 type_paths: type_paths.clone(),
415 error_type: error_type.to_string(),
416 };
417 let spec = TraitBridgeSpec {
418 trait_def: trait_type,
419 bridge_config: bridge_cfg,
420 core_import,
421 wrapper_prefix: "Php",
422 type_paths,
423 error_type: error_type.to_string(),
424 error_constructor: error_constructor.to_string(),
425 };
426 gen_bridge_all(&spec, &generator)
427 }
428}
429
430fn gen_visitor_bridge(
435 trait_type: &TypeDef,
436 _bridge_cfg: &TraitBridgeConfig,
437 struct_name: &str,
438 trait_path: &str,
439 type_paths: &HashMap<String, String>,
440) -> String {
441 let mut out = String::with_capacity(4096);
442 let core_crate = trait_path
443 .split("::")
444 .next()
445 .unwrap_or("html_to_markdown_rs")
446 .to_string();
447 writeln!(out, "fn nodecontext_to_php_array(").unwrap();
449 writeln!(out, " ctx: &{core_crate}::visitor::NodeContext,").unwrap();
450 writeln!(out, ") -> ext_php_rs::boxed::ZBox<ext_php_rs::types::ZendHashTable> {{").unwrap();
451 writeln!(out, " let mut arr = ext_php_rs::types::ZendHashTable::new();").unwrap();
452 writeln!(
453 out,
454 " arr.insert(\"nodeType\", ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", ctx.node_type)).unwrap_or_default()).ok();"
455 )
456 .unwrap();
457 writeln!(
458 out,
459 " arr.insert(\"tagName\", ext_php_rs::types::Zval::try_from(ctx.tag_name.clone()).unwrap_or_default()).ok();"
460 )
461 .unwrap();
462 writeln!(
463 out,
464 " arr.insert(\"depth\", ext_php_rs::types::Zval::try_from(ctx.depth as i64).unwrap_or_default()).ok();"
465 )
466 .unwrap();
467 writeln!(
468 out,
469 " arr.insert(\"indexInParent\", ext_php_rs::types::Zval::try_from(ctx.index_in_parent as i64).unwrap_or_default()).ok();"
470 )
471 .unwrap();
472 writeln!(
473 out,
474 " arr.insert(\"isInline\", ext_php_rs::types::Zval::try_from(ctx.is_inline).unwrap_or_default()).ok();"
475 )
476 .unwrap();
477 writeln!(out, " if let Some(ref pt) = ctx.parent_tag {{").unwrap();
478 writeln!(
479 out,
480 " arr.insert(\"parentTag\", ext_php_rs::types::Zval::try_from(pt.clone()).unwrap_or_default()).ok();"
481 )
482 .unwrap();
483 writeln!(out, " }}").unwrap();
484 writeln!(out, " let mut attrs = ext_php_rs::types::ZendHashTable::new();").unwrap();
485 writeln!(out, " for (k, v) in &ctx.attributes {{").unwrap();
486 writeln!(
487 out,
488 " attrs.insert(k.as_str(), ext_php_rs::types::Zval::try_from(v.clone()).unwrap_or_default()).ok();"
489 )
490 .unwrap();
491 writeln!(out, " }}").unwrap();
492 writeln!(out, " let mut attrs_zval = ext_php_rs::types::Zval::new();").unwrap();
493 writeln!(out, " attrs_zval.set_hashtable(attrs);").unwrap();
494 writeln!(out, " arr.insert(\"attributes\", attrs_zval).ok();").unwrap();
495 writeln!(out, " arr").unwrap();
496 writeln!(out, "}}").unwrap();
497 writeln!(out).unwrap();
498
499 writeln!(out, "pub struct {struct_name} {{").unwrap();
503 writeln!(out, " php_obj: *mut ext_php_rs::types::ZendObject,").unwrap();
504 writeln!(out, " cached_name: String,").unwrap();
505 writeln!(out, "}}").unwrap();
506 writeln!(out).unwrap();
507
508 writeln!(out, "// SAFETY: PHP objects are single-threaded; the bridge is used").unwrap();
511 writeln!(out, "// only within a single PHP request, never across threads.").unwrap();
512 writeln!(out, "unsafe impl Send for {struct_name} {{}}").unwrap();
513 writeln!(out, "unsafe impl Sync for {struct_name} {{}}").unwrap();
514 writeln!(out).unwrap();
515
516 writeln!(out, "impl Clone for {struct_name} {{").unwrap();
517 writeln!(out, " fn clone(&self) -> Self {{").unwrap();
518 writeln!(out, " Self {{").unwrap();
519 writeln!(out, " php_obj: self.php_obj,").unwrap();
520 writeln!(out, " cached_name: self.cached_name.clone(),").unwrap();
521 writeln!(out, " }}").unwrap();
522 writeln!(out, " }}").unwrap();
523 writeln!(out, "}}").unwrap();
524 writeln!(out).unwrap();
525
526 writeln!(out, "impl std::fmt::Debug for {struct_name} {{").unwrap();
528 writeln!(
529 out,
530 " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
531 )
532 .unwrap();
533 writeln!(out, " write!(f, \"{struct_name}\")").unwrap();
534 writeln!(out, " }}").unwrap();
535 writeln!(out, "}}").unwrap();
536 writeln!(out).unwrap();
537
538 writeln!(out, "impl {struct_name} {{").unwrap();
542 writeln!(
543 out,
544 " pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
545 )
546 .unwrap();
547 writeln!(out, " let cached_name = php_obj").unwrap();
548 writeln!(out, " .try_call_method(\"name\", vec![])").unwrap();
549 writeln!(out, " .ok()").unwrap();
550 writeln!(out, " .and_then(|v| v.string())").unwrap();
551 writeln!(out, " .unwrap_or(\"unknown\".into())").unwrap();
552 writeln!(out, " .to_string();").unwrap();
553 writeln!(out, " Self {{ php_obj: php_obj as *mut _, cached_name }}").unwrap();
554 writeln!(out, " }}").unwrap();
555 writeln!(out, "}}").unwrap();
556 writeln!(out).unwrap();
557
558 writeln!(out, "impl {trait_path} for {struct_name} {{").unwrap();
560 for method in &trait_type.methods {
561 if method.trait_source.is_some() {
562 continue;
563 }
564 gen_visitor_method_php(&mut out, method, type_paths);
565 }
566 writeln!(out, "}}").unwrap();
567 writeln!(out).unwrap();
568
569 out
570}
571
572fn gen_visitor_method_php(out: &mut String, method: &MethodDef, type_paths: &HashMap<String, String>) {
574 let name = &method.name;
575 let php_name = to_camel_case(name);
576
577 let mut sig_parts = vec!["&mut self".to_string()];
578 for p in &method.params {
579 let ty_str = visitor_param_type(&p.ty, p.is_ref, p.optional, type_paths);
580 sig_parts.push(format!("{}: {}", p.name, ty_str));
581 }
582 let sig = sig_parts.join(", ");
583
584 let ret_ty = match &method.return_type {
585 TypeRef::Named(n) => type_paths.get(n).cloned().unwrap_or_else(|| n.clone()),
586 other => param_type(other, "", false, type_paths),
587 };
588
589 writeln!(out, " fn {name}({sig}) -> {ret_ty} {{").unwrap();
590
591 writeln!(
593 out,
594 " // SAFETY: php_obj is a valid ZendObject pointer for the duration of this call."
595 )
596 .unwrap();
597 writeln!(out, " let php_obj_ref = unsafe {{ &mut *self.php_obj }};").unwrap();
598
599 let has_args = !method.params.is_empty();
601 if has_args {
602 writeln!(out, " let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").unwrap();
603 for p in &method.params {
604 if let TypeRef::Named(n) = &p.ty {
605 if n == "NodeContext" {
606 writeln!(
607 out,
608 " let ctx_arr = nodecontext_to_php_array({}{});",
609 if p.is_ref { "" } else { "&" },
610 p.name
611 )
612 .unwrap();
613 writeln!(
614 out,
615 " args.push(ext_php_rs::convert::IntoZval::into_zval(ctx_arr, false).unwrap_or_default());"
616 )
617 .unwrap();
618 continue;
619 }
620 }
621 if p.optional && matches!(&p.ty, TypeRef::String) && p.is_ref {
624 writeln!(
625 out,
626 " 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() }});",
627 p.name
628 )
629 .unwrap();
630 continue;
631 }
632 if matches!(&p.ty, TypeRef::String) {
633 if p.is_ref {
634 writeln!(
635 out,
636 " args.push(ext_php_rs::types::Zval::try_from({}.to_string()).unwrap_or_default());",
637 p.name
638 )
639 .unwrap();
640 } else {
641 writeln!(
642 out,
643 " args.push(ext_php_rs::types::Zval::try_from({}.clone()).unwrap_or_default());",
644 p.name
645 )
646 .unwrap();
647 }
648 continue;
649 }
650 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool)) {
651 writeln!(
652 out,
653 " {{ let mut _zv = ext_php_rs::types::Zval::new(); _zv.set_bool({}); args.push(_zv); }}",
654 p.name
655 )
656 .unwrap();
657 continue;
658 }
659 writeln!(
661 out,
662 " args.push(ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default());",
663 p.name
664 )
665 .unwrap();
666 }
667 }
668
669 if has_args {
673 writeln!(
674 out,
675 " let dyn_args: Vec<&dyn ext_php_rs::convert::IntoZvalDyn> = args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect();"
676 )
677 .unwrap();
678 }
679 let args_expr = if has_args { "dyn_args" } else { "vec![]" };
680 writeln!(
681 out,
682 " let result = php_obj_ref.try_call_method(\"{php_name}\", {args_expr});"
683 )
684 .unwrap();
685
686 writeln!(out, " match result {{").unwrap();
688 writeln!(out, " Err(_) => {ret_ty}::Continue,").unwrap();
689 writeln!(out, " Ok(val) => {{").unwrap();
690 writeln!(
691 out,
692 " let s = val.string().unwrap_or_default().to_lowercase();"
693 )
694 .unwrap();
695 writeln!(out, " match s.as_str() {{").unwrap();
696 writeln!(out, " \"continue\" => {ret_ty}::Continue,").unwrap();
697 writeln!(out, " \"skip\" => {ret_ty}::Skip,").unwrap();
698 writeln!(
699 out,
700 " \"preserve_html\" | \"preservehtml\" => {ret_ty}::PreserveHtml,"
701 )
702 .unwrap();
703 writeln!(out, " other => {ret_ty}::Custom(other.to_string()),").unwrap();
704 writeln!(out, " }}").unwrap();
705 writeln!(out, " }}").unwrap();
706 writeln!(out, " }}").unwrap();
707 writeln!(out, " }}").unwrap();
708 writeln!(out).unwrap();
709}
710
711#[allow(clippy::too_many_arguments)]
719pub fn gen_bridge_field_function(
720 func: &alef_core::ir::FunctionDef,
721 field_match: &BridgeFieldMatch<'_>,
722 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
723 opaque_types: &ahash::AHashSet<String>,
724 core_import: &str,
725) -> String {
726 use alef_core::ir::TypeRef;
727
728 let struct_name = format!("Php{}Bridge", field_match.bridge.trait_name);
729 let handle_path = format!("{core_import}::visitor::VisitorHandle");
730 let field_name = &field_match.field_name;
731 let extra_param = format!("{field_name}_obj");
732
733 let mut sig_parts = Vec::new();
735 let err_conv = ".map_err(|e| ext_php_rs::exception::PhpException::default(e.to_string()))";
736
737 for p in func.params.iter() {
738 let base = mapper.map_type(&p.ty);
739 let ty = match &p.ty {
740 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
741 if p.optional {
742 format!("Option<&mut {base}>")
743 } else {
744 format!("&mut {base}")
745 }
746 }
747 TypeRef::Optional(inner) => {
748 if let TypeRef::Named(n) = inner.as_ref() {
749 if !opaque_types.contains(n.as_str()) {
750 format!("Option<&mut {base}>")
751 } else if p.optional {
752 format!("Option<{base}>")
753 } else {
754 base
755 }
756 } else if p.optional {
757 format!("Option<{base}>")
758 } else {
759 base
760 }
761 }
762 _ => {
763 if p.optional {
764 format!("Option<{base}>")
765 } else {
766 base
767 }
768 }
769 };
770 sig_parts.push(format!("{}: {}", p.name, ty));
771 }
772 sig_parts.push(format!("{extra_param}: Option<&mut ext_php_rs::types::ZendObject>"));
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 bridge_wrap = format!(
781 "let {extra_param} = {extra_param}.map(|v| {{\n \
782 let bridge = {struct_name}::new(v);\n \
783 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
784 }});"
785 );
786
787 let opts_param_name = &func.params[field_match.param_index].name;
789 let opts_is_optional = field_match.param_is_optional;
790
791 let serde_bindings: String = func
792 .params
793 .iter()
794 .filter(|p| {
795 let named = match &p.ty {
796 TypeRef::Named(n) => Some(n.as_str()),
797 TypeRef::Optional(inner) => {
798 if let TypeRef::Named(n) = inner.as_ref() {
799 Some(n.as_str())
800 } else {
801 None
802 }
803 }
804 _ => None,
805 };
806 named.is_some_and(|n| !opaque_types.contains(n))
807 })
808 .map(|p| {
809 let name = &p.name;
810 let core_type_name = match &p.ty {
811 TypeRef::Named(n) => n.clone(),
812 TypeRef::Optional(inner) => {
813 if let TypeRef::Named(n) = inner.as_ref() {
814 n.clone()
815 } else {
816 String::new()
817 }
818 }
819 _ => String::new(),
820 };
821 let core_path = format!("{core_import}::{core_type_name}");
822 if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
823 format!(
825 "let mut {name}_core: Option<{core_path}> = {name}.map(|v| {{\n \
826 let json = serde_json::to_string(&v){err_conv}?;\n \
827 serde_json::from_str(&json){err_conv}\n \
828 }}).transpose()?;\n "
829 )
830 } else {
831 format!(
832 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n \
833 let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n "
834 )
835 }
836 })
837 .collect();
838
839 let visitor_attach = if opts_is_optional {
841 format!(
842 "if let (Some(ref mut opts), Some(v)) = ({opts_param_name}_core.as_mut(), {extra_param}) {{\n \
843 opts.{field_name} = Some(v);\n \
844 }}\n "
845 )
846 } else {
847 format!(
848 "if let Some(v) = {extra_param} {{\n \
849 {opts_param_name}_core.{field_name} = Some(v);\n \
850 }}\n "
851 )
852 };
853
854 let call_args: Vec<String> = func
856 .params
857 .iter()
858 .map(|p| match &p.ty {
859 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
860 if p.optional {
861 format!("{}.as_ref().map(|v| &v.inner)", p.name)
862 } else {
863 format!("&{}.inner", p.name)
864 }
865 }
866 TypeRef::Named(_) => format!("{}_core", p.name),
867 TypeRef::Optional(inner) => {
868 if let TypeRef::Named(n) = inner.as_ref() {
869 if opaque_types.contains(n.as_str()) {
870 format!("{}.as_ref().map(|v| &v.inner)", p.name)
871 } else {
872 format!("{}_core", p.name)
873 }
874 } else {
875 p.name.clone()
876 }
877 }
878 TypeRef::String | TypeRef::Char => {
879 if p.is_ref {
880 format!("&{}", p.name)
881 } else {
882 p.name.clone()
883 }
884 }
885 _ => p.name.clone(),
886 })
887 .collect();
888 let call_args_str = call_args.join(", ");
889
890 let core_fn_path = {
891 let path = func.rust_path.replace('-', "_");
892 if path.starts_with(core_import) {
893 path
894 } else {
895 format!("{core_import}::{}", func.name)
896 }
897 };
898 let core_call = format!("{core_fn_path}({call_args_str})");
899
900 let return_wrap = match &func.return_type {
901 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
902 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
903 }
904 TypeRef::Named(_) => "val.into()".to_string(),
905 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
906 _ => "val".to_string(),
907 };
908
909 let body = if func.error_type.is_some() {
910 if return_wrap == "val" {
911 format!("{bridge_wrap}\n {serde_bindings}{visitor_attach}{core_call}{err_conv}")
912 } else {
913 format!("{bridge_wrap}\n {serde_bindings}{visitor_attach}{core_call}.map(|val| {return_wrap}){err_conv}")
914 }
915 } else {
916 format!("{bridge_wrap}\n {serde_bindings}{visitor_attach}{core_call}")
917 };
918
919 let func_name = &func.name;
920 let mut out = String::with_capacity(1024);
921 if func.error_type.is_some() {
922 out.push_str("#[allow(clippy::missing_errors_doc)]\n");
923 }
924 out.push_str(&format!("pub fn {func_name}({params_str}) -> {ret} {{\n"));
925 out.push_str(&format!(" {body}\n"));
926 out.push_str("}\n");
927
928 out
929}
930
931#[allow(clippy::too_many_arguments)]
934pub fn gen_bridge_function(
935 func: &alef_core::ir::FunctionDef,
936 bridge_param_idx: usize,
937 bridge_cfg: &TraitBridgeConfig,
938 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
939 opaque_types: &ahash::AHashSet<String>,
940 core_import: &str,
941) -> String {
942 use alef_core::ir::TypeRef;
943
944 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
945 let handle_path = format!("{core_import}::visitor::VisitorHandle");
946 let param_name = &func.params[bridge_param_idx].name;
947 let bridge_param = &func.params[bridge_param_idx];
948 let is_optional = bridge_param.optional || matches!(&bridge_param.ty, TypeRef::Optional(_));
949
950 let mut sig_parts = Vec::new();
952 for (idx, p) in func.params.iter().enumerate() {
953 if idx == bridge_param_idx {
954 let php_obj_ty = "&mut ext_php_rs::types::ZendObject";
958 if is_optional {
959 sig_parts.push(format!("{}: Option<{php_obj_ty}>", p.name));
960 } else {
961 sig_parts.push(format!("{}: {php_obj_ty}", p.name));
962 }
963 } else {
964 let promoted = idx > bridge_param_idx || func.params[..idx].iter().any(|pp| pp.optional);
965 let base = mapper.map_type(&p.ty);
966 let ty = match &p.ty {
969 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
970 if p.optional || promoted {
971 format!("Option<&mut {base}>")
972 } else {
973 format!("&mut {base}")
974 }
975 }
976 TypeRef::Optional(inner) => {
977 if let TypeRef::Named(n) = inner.as_ref() {
978 if !opaque_types.contains(n.as_str()) {
979 format!("Option<&mut {base}>")
980 } else if p.optional || promoted {
981 format!("Option<{base}>")
982 } else {
983 base
984 }
985 } else if p.optional || promoted {
986 format!("Option<{base}>")
987 } else {
988 base
989 }
990 }
991 _ => {
992 if p.optional || promoted {
993 format!("Option<{base}>")
994 } else {
995 base
996 }
997 }
998 };
999 sig_parts.push(format!("{}: {}", p.name, ty));
1000 }
1001 }
1002
1003 let params_str = sig_parts.join(", ");
1004 let return_type = mapper.map_type(&func.return_type);
1005 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
1006
1007 let err_conv = ".map_err(|e| ext_php_rs::exception::PhpException::default(e.to_string()))";
1008
1009 let bridge_wrap = if is_optional {
1011 format!(
1012 "let {param_name} = {param_name}.map(|v| {{\n \
1013 let bridge = {struct_name}::new(v);\n \
1014 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
1015 }});"
1016 )
1017 } else {
1018 format!(
1019 "let {param_name} = {{\n \
1020 let bridge = {struct_name}::new({param_name});\n \
1021 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
1022 }};"
1023 )
1024 };
1025
1026 let serde_bindings: String = func
1028 .params
1029 .iter()
1030 .enumerate()
1031 .filter(|(idx, p)| {
1032 if *idx == bridge_param_idx {
1033 return false;
1034 }
1035 let named = match &p.ty {
1036 TypeRef::Named(n) => Some(n.as_str()),
1037 TypeRef::Optional(inner) => {
1038 if let TypeRef::Named(n) = inner.as_ref() {
1039 Some(n.as_str())
1040 } else {
1041 None
1042 }
1043 }
1044 _ => None,
1045 };
1046 named.is_some_and(|n| !opaque_types.contains(n))
1047 })
1048 .map(|(_, p)| {
1049 let name = &p.name;
1050 let core_path = format!(
1051 "{core_import}::{}",
1052 match &p.ty {
1053 TypeRef::Named(n) => n.clone(),
1054 TypeRef::Optional(inner) =>
1055 if let TypeRef::Named(n) = inner.as_ref() {
1056 n.clone()
1057 } else {
1058 String::new()
1059 },
1060 _ => String::new(),
1061 }
1062 );
1063 if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
1064 format!(
1065 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n \
1066 let json = serde_json::to_string(&v){err_conv}?;\n \
1067 serde_json::from_str(&json){err_conv}\n \
1068 }}).transpose()?;\n "
1069 )
1070 } else {
1071 format!(
1072 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n \
1073 let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n "
1074 )
1075 }
1076 })
1077 .collect();
1078
1079 let call_args: Vec<String> = func
1081 .params
1082 .iter()
1083 .enumerate()
1084 .map(|(idx, p)| {
1085 if idx == bridge_param_idx {
1086 return p.name.clone();
1087 }
1088 match &p.ty {
1089 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
1090 if p.optional {
1091 format!("{}.as_ref().map(|v| &v.inner)", p.name)
1092 } else {
1093 format!("&{}.inner", p.name)
1094 }
1095 }
1096 TypeRef::Named(_) => format!("{}_core", p.name),
1097 TypeRef::Optional(inner) => {
1098 if let TypeRef::Named(n) = inner.as_ref() {
1099 if opaque_types.contains(n.as_str()) {
1100 format!("{}.as_ref().map(|v| &v.inner)", p.name)
1101 } else {
1102 format!("{}_core", p.name)
1103 }
1104 } else {
1105 p.name.clone()
1106 }
1107 }
1108 TypeRef::String | TypeRef::Char => {
1109 if p.is_ref {
1110 format!("&{}", p.name)
1111 } else {
1112 p.name.clone()
1113 }
1114 }
1115 _ => p.name.clone(),
1116 }
1117 })
1118 .collect();
1119 let call_args_str = call_args.join(", ");
1120
1121 let core_fn_path = {
1122 let path = func.rust_path.replace('-', "_");
1123 if path.starts_with(core_import) {
1124 path
1125 } else {
1126 format!("{core_import}::{}", func.name)
1127 }
1128 };
1129 let core_call = format!("{core_fn_path}({call_args_str})");
1130
1131 let return_wrap = match &func.return_type {
1132 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
1133 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
1134 }
1135 TypeRef::Named(_) => "val.into()".to_string(),
1136 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
1137 _ => "val".to_string(),
1138 };
1139
1140 let body = if func.error_type.is_some() {
1141 if return_wrap == "val" {
1142 format!("{bridge_wrap}\n {serde_bindings}{core_call}{err_conv}")
1143 } else {
1144 format!("{bridge_wrap}\n {serde_bindings}{core_call}.map(|val| {return_wrap}){err_conv}")
1145 }
1146 } else {
1147 format!("{bridge_wrap}\n {serde_bindings}{core_call}")
1148 };
1149
1150 let func_name = &func.name;
1151 let mut out = String::with_capacity(1024);
1152 if func.error_type.is_some() {
1153 writeln!(out, "#[allow(clippy::missing_errors_doc)]").ok();
1154 }
1155 writeln!(out, "pub fn {func_name}({params_str}) -> {ret} {{").ok();
1156 writeln!(out, " {body}").ok();
1157 writeln!(out, "}}").ok();
1158
1159 out
1160}