1use heck::ToUpperCamelCase;
25use weaveffi_ir::ir::{
26 Api, CallbackDef, EnumDef, ErrorDomain, Function, InterfaceDef, ListenerDef, Module, StructDef,
27 TypeRef,
28};
29
30use crate::abi::{
31 self, async_callback_params, async_input_params, context_param, error_out_param, lower_param,
32 lower_return, sync_signature, AbiParam, CType, ConstPos,
33};
34
35#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct AbiFn {
39 pub symbol: String,
41 pub params: Vec<AbiParam>,
43 pub ret: CType,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum CallShape {
51 Sync(AbiFn),
53 Async(AsyncBinding),
55 Iterator(IteratorBinding),
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct AsyncBinding {
62 pub launch: AbiFn,
65 pub callback_type: String,
68 pub callback_params: Vec<AbiParam>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct IteratorBinding {
76 pub elem: TypeRef,
78 pub iter_tag: String,
80 pub launch: AbiFn,
82 pub next: AbiFn,
84 pub destroy_symbol: String,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct ParamBinding {
91 pub name: String,
93 pub ty: TypeRef,
95 pub mutable: bool,
97 pub doc: Option<String>,
99 pub abi: Vec<AbiParam>,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct FnBinding {
112 pub name: String,
114 pub doc: Option<String>,
116 pub deprecated: Option<String>,
118 pub since: Option<String>,
120 pub cancellable: bool,
122 pub is_async: bool,
124 pub throws: bool,
130 pub has_self: bool,
133 pub params: Vec<ParamBinding>,
135 pub ret: Option<TypeRef>,
139 pub c_base: String,
143 pub shape: CallShape,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct FieldBinding {
150 pub name: String,
152 pub doc: Option<String>,
154 pub ty: TypeRef,
156 pub getter_symbol: String,
159 pub getter_ret: CType,
161 pub getter_out_params: Vec<AbiParam>,
163 pub value_params: Vec<AbiParam>,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct BuilderBinding {
171 pub builder_tag: String,
173 pub new_symbol: String,
175 pub build_symbol: String,
177 pub destroy_symbol: String,
179 pub setters: Vec<(String, String)>,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct StructBinding {
187 pub name: String,
189 pub doc: Option<String>,
191 pub c_tag: String,
193 pub fields: Vec<FieldBinding>,
195 pub create: AbiFn,
197 pub destroy_symbol: String,
199 pub builder: Option<BuilderBinding>,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
212pub struct EnumBinding {
213 pub name: String,
215 pub doc: Option<String>,
217 pub c_tag: String,
219 pub variants: Vec<EnumVariantBinding>,
222 pub rich: Option<RichEnumBinding>,
224}
225
226impl EnumBinding {
227 pub fn is_rich(&self) -> bool {
229 self.rich.is_some()
230 }
231}
232
233#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct EnumVariantBinding {
236 pub name: String,
238 pub value: i32,
240 pub doc: Option<String>,
242 pub c_const: String,
244}
245
246#[derive(Debug, Clone, PartialEq, Eq)]
249pub struct RichEnumBinding {
250 pub tag_symbol: String,
254 pub destroy_symbol: String,
256 pub variants: Vec<RichVariantBinding>,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
264pub struct RichVariantBinding {
265 pub name: String,
267 pub doc: Option<String>,
269 pub value: i32,
271 pub c_const: String,
273 pub create: AbiFn,
276 pub fields: Vec<FieldBinding>,
279}
280
281#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct CallbackBinding {
284 pub name: String,
286 pub doc: Option<String>,
288 pub c_fn_type: String,
290 pub params: Vec<ParamBinding>,
292 pub abi_params: Vec<AbiParam>,
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct ListenerBinding {
299 pub name: String,
301 pub doc: Option<String>,
303 pub event_callback: String,
305 pub callback_c_fn_type: String,
307 pub register_symbol: String,
309 pub unregister_symbol: String,
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
321pub struct InterfaceBinding {
322 pub name: String,
324 pub doc: Option<String>,
326 pub c_tag: String,
328 pub constructors: Vec<FnBinding>,
330 pub methods: Vec<FnBinding>,
332 pub statics: Vec<FnBinding>,
334 pub destroy_symbol: String,
336}
337
338#[derive(Debug, Clone, PartialEq, Eq)]
340pub struct ErrorCodeBinding {
341 pub name: String,
343 pub value: i32,
345 pub message: String,
347 pub doc: Option<String>,
349 pub c_const: String,
351}
352
353#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct ErrorBinding {
362 pub name: String,
364 pub type_name: String,
368 pub owner_path: String,
370 pub declared_here: bool,
373 pub c_tag: String,
376 pub codes: Vec<ErrorCodeBinding>,
378}
379
380#[derive(Debug, Clone, PartialEq, Eq)]
382pub struct ModuleBinding {
383 pub name: String,
385 pub segments: Vec<String>,
387 pub path: String,
389 pub doc: Option<String>,
391 pub error: Option<ErrorBinding>,
395 pub enums: Vec<EnumBinding>,
397 pub structs: Vec<StructBinding>,
399 pub interfaces: Vec<InterfaceBinding>,
401 pub callbacks: Vec<CallbackBinding>,
403 pub listeners: Vec<ListenerBinding>,
405 pub functions: Vec<FnBinding>,
407}
408
409impl ModuleBinding {
410 pub fn callback(&self, name: &str) -> Option<&CallbackBinding> {
412 self.callbacks.iter().find(|c| c.name == name)
413 }
414
415 pub fn is_empty(&self) -> bool {
417 self.enums.is_empty()
418 && self.structs.is_empty()
419 && self.interfaces.is_empty()
420 && self.callbacks.is_empty()
421 && self.listeners.is_empty()
422 && self.functions.is_empty()
423 && !self.declares_error()
424 }
425
426 pub fn declares_error(&self) -> bool {
429 self.error.as_ref().is_some_and(|e| e.declared_here)
430 }
431
432 pub fn callables(&self) -> impl Iterator<Item = &FnBinding> {
435 self.functions
436 .iter()
437 .chain(self.interfaces.iter().flat_map(|i| {
438 i.constructors
439 .iter()
440 .chain(i.methods.iter())
441 .chain(i.statics.iter())
442 }))
443 }
444}
445
446#[derive(Debug, Clone, PartialEq, Eq)]
448pub struct BindingModel {
449 pub prefix: String,
451 pub version: String,
453 pub modules: Vec<ModuleBinding>,
455}
456
457impl BindingModel {
458 pub fn build(api: &Api, prefix: &str) -> Self {
463 let mut modules = Vec::new();
464 for m in &api.modules {
465 lower_module(m, &[], prefix, None, &mut modules);
466 }
467 Self {
468 prefix: prefix.to_string(),
469 version: api.version.clone(),
470 modules,
471 }
472 }
473
474 pub fn functions(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
476 self.modules
477 .iter()
478 .flat_map(|m| m.functions.iter().map(move |f| (m, f)))
479 }
480}
481
482fn lower_module(
487 module: &Module,
488 parent: &[String],
489 prefix: &str,
490 inherited_error: Option<&ErrorBinding>,
491 out: &mut Vec<ModuleBinding>,
492) {
493 let mut segments = parent.to_vec();
494 segments.push(module.name.clone());
495 let path = segments.join("_");
496
497 let error = match &module.errors {
498 Some(domain) => Some(lower_error_domain(domain, &path, prefix)),
499 None => inherited_error.cloned().map(|mut e| {
500 e.declared_here = false;
501 e
502 }),
503 };
504
505 let enums = module
506 .enums
507 .iter()
508 .map(|e| lower_enum(e, &path, prefix))
509 .collect();
510 let structs = module
511 .structs
512 .iter()
513 .map(|s| lower_struct(s, &path, prefix))
514 .collect();
515 let interfaces = module
516 .interfaces
517 .iter()
518 .map(|i| lower_interface(i, &path, prefix))
519 .collect();
520 let callbacks: Vec<CallbackBinding> = module
521 .callbacks
522 .iter()
523 .map(|c| lower_callback(c, &path, prefix))
524 .collect();
525 let listeners = module
526 .listeners
527 .iter()
528 .map(|l| lower_listener(l, &path, prefix))
529 .collect();
530 let functions = module
531 .functions
532 .iter()
533 .map(|f| lower_function(f, &path, prefix))
534 .collect();
535
536 let doc = module.functions.iter().find_map(|f| f.doc.clone());
539
540 out.push(ModuleBinding {
541 name: module.name.clone(),
542 segments: segments.clone(),
543 path,
544 doc,
545 error: error.clone(),
546 enums,
547 structs,
548 interfaces,
549 callbacks,
550 listeners,
551 functions,
552 });
553
554 for child in &module.modules {
555 lower_module(child, &segments, prefix, error.as_ref(), out);
556 }
557}
558
559fn lower_error_domain(domain: &ErrorDomain, path: &str, prefix: &str) -> ErrorBinding {
560 let c_tag = format!("{prefix}_{path}_{}", domain.name);
561 ErrorBinding {
562 name: domain.name.clone(),
563 type_name: crate::errors::type_name(&domain.name, "Error"),
564 owner_path: path.to_string(),
565 declared_here: true,
566 c_tag: c_tag.clone(),
567 codes: domain
568 .codes
569 .iter()
570 .map(|c| ErrorCodeBinding {
571 name: c.name.clone(),
572 value: c.code,
573 message: c.message.clone(),
574 doc: c.doc.clone(),
575 c_const: format!("{c_tag}_{}", c.name),
576 })
577 .collect(),
578 }
579}
580
581fn lower_interface(iface: &InterfaceDef, path: &str, prefix: &str) -> InterfaceBinding {
585 let c_tag = format!("{prefix}_{path}_{}", iface.name);
586 let self_slot = AbiParam::new(
587 "self",
588 CType::Ptr {
589 konst: ConstPos::West,
590 pointee: Box::new(CType::StructTag {
591 module: path.to_string(),
592 name: iface.name.clone(),
593 }),
594 },
595 );
596 let constructors = iface
597 .constructors
598 .iter()
599 .map(|c| {
600 let mut f = c.clone();
603 f.returns = Some(TypeRef::Interface(iface.name.clone()));
604 lower_callable(&f, path, prefix, &member_base(&c_tag, &c.name), None)
605 })
606 .collect();
607 let methods = iface
608 .methods
609 .iter()
610 .map(|m| {
611 lower_callable(
612 m,
613 path,
614 prefix,
615 &member_base(&c_tag, &m.name),
616 Some(self_slot.clone()),
617 )
618 })
619 .collect();
620 let statics = iface
621 .statics
622 .iter()
623 .map(|s| lower_callable(s, path, prefix, &member_base(&c_tag, &s.name), None))
624 .collect();
625 InterfaceBinding {
626 name: iface.name.clone(),
627 doc: iface.doc.clone(),
628 c_tag: c_tag.clone(),
629 constructors,
630 methods,
631 statics,
632 destroy_symbol: format!("{c_tag}_destroy"),
633 }
634}
635
636fn member_base(c_tag: &str, name: &str) -> String {
638 format!("{c_tag}_{name}")
639}
640
641fn lower_param_binding(p: &weaveffi_ir::ir::Param, module: &str) -> ParamBinding {
642 ParamBinding {
643 name: p.name.clone(),
644 ty: p.ty.clone(),
645 mutable: p.mutable,
646 doc: p.doc.clone(),
647 abi: lower_param(&p.name, &p.ty, module, p.mutable),
648 }
649}
650
651fn lower_enum(e: &EnumDef, path: &str, prefix: &str) -> EnumBinding {
652 let c_tag = format!("{prefix}_{path}_{}", e.name);
653 let variants = e
654 .variants
655 .iter()
656 .map(|v| EnumVariantBinding {
657 name: v.name.clone(),
658 value: v.value,
659 doc: v.doc.clone(),
660 c_const: format!("{c_tag}_{}", v.name),
661 })
662 .collect();
663
664 let rich = e.is_rich().then(|| {
668 let variants = e
669 .variants
670 .iter()
671 .map(|v| {
672 let fields: Vec<FieldBinding> = v
673 .fields
674 .iter()
675 .map(|f| {
676 let r = lower_return(&f.ty, path);
677 FieldBinding {
678 name: f.name.clone(),
679 doc: f.doc.clone(),
680 ty: f.ty.clone(),
681 getter_symbol: format!("{c_tag}_{}_get_{}", v.name, f.name),
682 getter_ret: r.ret,
683 getter_out_params: r.out_params,
684 value_params: lower_param(&f.name, &f.ty, path, false),
685 }
686 })
687 .collect();
688 let mut create_params: Vec<AbiParam> = v
689 .fields
690 .iter()
691 .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
692 .collect();
693 create_params.push(error_out_param());
694 let create = AbiFn {
695 symbol: format!("{c_tag}_{}_new", v.name),
696 params: create_params,
697 ret: CType::ptr(CType::Named(format!("{path}_{}", e.name))),
698 };
699 RichVariantBinding {
700 name: v.name.clone(),
701 doc: v.doc.clone(),
702 value: v.value,
703 c_const: format!("{c_tag}_{}", v.name),
704 create,
705 fields,
706 }
707 })
708 .collect();
709 RichEnumBinding {
710 tag_symbol: format!("{c_tag}_tag"),
711 destroy_symbol: format!("{c_tag}_destroy"),
712 variants,
713 }
714 });
715
716 EnumBinding {
717 name: e.name.clone(),
718 doc: e.doc.clone(),
719 c_tag,
720 variants,
721 rich,
722 }
723}
724
725fn lower_struct(s: &StructDef, path: &str, prefix: &str) -> StructBinding {
726 let c_tag = format!("{prefix}_{path}_{}", s.name);
727
728 let fields: Vec<FieldBinding> = s
729 .fields
730 .iter()
731 .map(|f| {
732 let r = lower_return(&f.ty, path);
733 FieldBinding {
734 name: f.name.clone(),
735 doc: f.doc.clone(),
736 ty: f.ty.clone(),
737 getter_symbol: format!("{c_tag}_get_{}", f.name),
738 getter_ret: r.ret,
739 getter_out_params: r.out_params,
740 value_params: lower_param(&f.name, &f.ty, path, false),
741 }
742 })
743 .collect();
744
745 let mut create_params: Vec<AbiParam> = s
747 .fields
748 .iter()
749 .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
750 .collect();
751 create_params.push(error_out_param());
752 let create = AbiFn {
753 symbol: format!("{c_tag}_create"),
754 params: create_params,
755 ret: CType::ptr(CType::Named(format!("{path}_{}", s.name))),
756 };
757
758 let builder = s.builder.then(|| {
759 let builder_tag = format!("{c_tag}Builder");
760 let setters = s
761 .fields
762 .iter()
763 .map(|f| (f.name.clone(), format!("{c_tag}_Builder_set_{}", f.name)))
764 .collect();
765 BuilderBinding {
766 builder_tag,
767 new_symbol: format!("{c_tag}_Builder_new"),
768 build_symbol: format!("{c_tag}_Builder_build"),
769 destroy_symbol: format!("{c_tag}_Builder_destroy"),
770 setters,
771 }
772 });
773
774 StructBinding {
775 name: s.name.clone(),
776 doc: s.doc.clone(),
777 c_tag: c_tag.clone(),
778 fields,
779 create,
780 destroy_symbol: format!("{c_tag}_destroy"),
781 builder,
782 }
783}
784
785fn lower_callback(c: &CallbackDef, path: &str, prefix: &str) -> CallbackBinding {
786 let params: Vec<ParamBinding> = c
787 .params
788 .iter()
789 .map(|p| lower_param_binding(p, path))
790 .collect();
791 let mut abi_params: Vec<AbiParam> = params.iter().flat_map(|p| p.abi.clone()).collect();
792 abi_params.push(context_param());
793 CallbackBinding {
794 name: c.name.clone(),
795 doc: c.doc.clone(),
796 c_fn_type: format!("{prefix}_{path}_{}_fn", c.name),
797 params,
798 abi_params,
799 }
800}
801
802fn lower_listener(l: &ListenerDef, path: &str, prefix: &str) -> ListenerBinding {
803 ListenerBinding {
804 name: l.name.clone(),
805 doc: l.doc.clone(),
806 event_callback: l.event_callback.clone(),
807 callback_c_fn_type: format!("{prefix}_{path}_{}_fn", l.event_callback),
808 register_symbol: format!("{prefix}_{path}_register_{}", l.name),
809 unregister_symbol: format!("{prefix}_{path}_unregister_{}", l.name),
810 }
811}
812
813fn lower_function(f: &Function, path: &str, prefix: &str) -> FnBinding {
814 let c_base = format!("{prefix}_{path}_{}", f.name);
815 lower_callable(f, path, prefix, &c_base, None)
816}
817
818fn lower_callable(
823 f: &Function,
824 path: &str,
825 prefix: &str,
826 c_base: &str,
827 self_slot: Option<AbiParam>,
828) -> FnBinding {
829 let params: Vec<ParamBinding> = f
830 .params
831 .iter()
832 .map(|p| lower_param_binding(p, path))
833 .collect();
834 let core_base = c_base
837 .strip_prefix(&format!("{prefix}_"))
838 .expect("c_base always starts with the symbol prefix")
839 .to_string();
840 let with_self = |mut params: Vec<AbiParam>| {
841 if let Some(s) = &self_slot {
842 params.insert(0, s.clone());
843 }
844 params
845 };
846
847 let shape = if let Some(TypeRef::Iterator(inner)) = &f.returns {
848 let pascal = f.name.to_upper_camel_case();
849 let owner = &core_base[..core_base.len() - f.name.len() - 1];
852 let iter_core = format!("{owner}_{pascal}Iterator");
853 let iter_tag = format!("{prefix}_{iter_core}");
854
855 let mut launch_params: Vec<AbiParam> = f
857 .params
858 .iter()
859 .flat_map(|p| lower_param(&p.name, &p.ty, path, p.mutable))
860 .collect();
861 launch_params.push(error_out_param());
862 let launch = AbiFn {
863 symbol: c_base.to_string(),
864 params: with_self(launch_params),
865 ret: CType::ptr(CType::Named(iter_core.clone())),
866 };
867
868 let item = lower_return(inner, path);
870 let mut next_params = vec![
871 AbiParam::new("iter", CType::ptr(CType::Named(iter_core.clone()))),
872 AbiParam::new("out_item", CType::ptr(item.ret)),
873 ];
874 next_params.extend(item.out_params);
875 next_params.push(error_out_param());
876 let next = AbiFn {
877 symbol: format!("{iter_tag}_next"),
878 params: next_params,
879 ret: CType::Int32,
880 };
881
882 CallShape::Iterator(IteratorBinding {
883 elem: (**inner).clone(),
884 iter_tag: iter_tag.clone(),
885 launch,
886 next,
887 destroy_symbol: format!("{iter_tag}_destroy"),
888 })
889 } else if f.r#async {
890 let callback_type = format!("{c_base}_callback");
891 let mut launch_params = async_input_params(f, path);
892 launch_params.push(AbiParam::new(
893 "callback",
894 CType::Named(format!("{core_base}_callback")),
895 ));
896 launch_params.push(context_param());
897 let launch = AbiFn {
898 symbol: format!("{c_base}_async"),
899 params: with_self(launch_params),
900 ret: CType::Void,
901 };
902 CallShape::Async(AsyncBinding {
903 launch,
904 callback_type,
905 callback_params: async_callback_params(f.returns.as_ref(), path),
906 })
907 } else {
908 let sig = sync_signature(&f.params, f.returns.as_ref(), path);
909 CallShape::Sync(AbiFn {
910 symbol: c_base.to_string(),
911 params: with_self(sig.params),
912 ret: sig.ret,
913 })
914 };
915
916 FnBinding {
917 name: f.name.clone(),
918 doc: f.doc.clone(),
919 deprecated: f.deprecated.clone(),
920 since: f.since.clone(),
921 cancellable: f.cancellable,
922 is_async: f.r#async,
923 throws: f.throws,
924 has_self: self_slot.is_some(),
925 params,
926 ret: f.returns.clone(),
927 c_base: c_base.to_string(),
928 shape,
929 }
930}
931
932pub fn iterator_item_ctype(elem: &TypeRef, module: &str) -> CType {
935 abi::lower_return(elem, module).ret
936}
937
938#[cfg(test)]
939mod tests {
940 use super::*;
941 use weaveffi_ir::ir::{
942 CallbackDef, EnumDef, EnumVariant, Function, ListenerDef, Module, Param, StructDef,
943 StructField,
944 };
945
946 fn param(name: &str, ty: TypeRef) -> Param {
947 Param {
948 name: name.into(),
949 ty,
950 mutable: false,
951 doc: None,
952 }
953 }
954
955 fn func(name: &str, params: Vec<Param>, returns: Option<TypeRef>) -> Function {
956 Function {
957 name: name.into(),
958 params,
959 returns,
960 doc: None,
961 throws: false,
962 r#async: false,
963 cancellable: false,
964 deprecated: None,
965 since: None,
966 }
967 }
968
969 fn module(name: &str) -> Module {
970 Module {
971 name: name.into(),
972 functions: vec![],
973 interfaces: vec![],
974 structs: vec![],
975 enums: vec![],
976 callbacks: vec![],
977 listeners: vec![],
978 errors: None,
979 modules: vec![],
980 }
981 }
982
983 fn api(modules: Vec<Module>) -> Api {
984 Api {
985 version: "0.5.0".into(),
986 modules,
987 generators: None,
988 package: None,
989 }
990 }
991
992 #[test]
993 fn sync_function_symbol_and_sig() {
994 let m = Module {
995 functions: vec![func(
996 "add",
997 vec![param("a", TypeRef::I32), param("b", TypeRef::I32)],
998 Some(TypeRef::I32),
999 )],
1000 ..module("math")
1001 };
1002 let model = BindingModel::build(&api(vec![m]), "weaveffi");
1003 let f = &model.modules[0].functions[0];
1004 assert_eq!(f.c_base, "weaveffi_math_add");
1005 match &f.shape {
1006 CallShape::Sync(abi) => {
1007 assert_eq!(abi.symbol, "weaveffi_math_add");
1008 assert_eq!(abi.ret, CType::Int32);
1009 let rendered: Vec<String> = abi
1010 .params
1011 .iter()
1012 .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
1013 .collect();
1014 assert_eq!(
1015 rendered,
1016 ["int32_t a", "int32_t b", "weaveffi_error* out_err"]
1017 );
1018 }
1019 _ => panic!("expected sync"),
1020 }
1021 }
1022
1023 #[test]
1024 fn prefix_is_honored_everywhere() {
1025 let m = Module {
1026 functions: vec![func("ping", vec![], None)],
1027 ..module("net")
1028 };
1029 let model = BindingModel::build(&api(vec![m]), "acme");
1030 let f = &model.modules[0].functions[0];
1031 assert_eq!(f.c_base, "acme_net_ping");
1032 }
1033
1034 #[test]
1035 fn async_function_has_launch_and_callback() {
1036 let m = Module {
1037 functions: vec![Function {
1038 cancellable: true,
1039 throws: false,
1040 r#async: true,
1041 ..func(
1042 "fetch",
1043 vec![param("id", TypeRef::I64)],
1044 Some(TypeRef::StringUtf8),
1045 )
1046 }],
1047 ..module("net")
1048 };
1049 let model = BindingModel::build(&api(vec![m]), "weaveffi");
1050 match &model.modules[0].functions[0].shape {
1051 CallShape::Async(a) => {
1052 assert_eq!(a.launch.symbol, "weaveffi_net_fetch_async");
1053 assert_eq!(a.callback_type, "weaveffi_net_fetch_callback");
1054 let last_two: Vec<&str> = a
1055 .launch
1056 .params
1057 .iter()
1058 .rev()
1059 .take(2)
1060 .map(|p| p.name.as_str())
1061 .collect();
1062 assert_eq!(last_two, ["context", "callback"]);
1063 assert!(a.launch.params.iter().any(|p| p.name == "cancel_token"));
1065 assert_eq!(a.callback_params[0].name, "context");
1067 assert_eq!(a.callback_params[1].name, "err");
1068 }
1069 _ => panic!("expected async"),
1070 }
1071 }
1072
1073 #[test]
1074 fn iterator_function_has_next_and_destroy() {
1075 let m = Module {
1076 functions: vec![func(
1077 "get_messages",
1078 vec![],
1079 Some(TypeRef::Iterator(Box::new(TypeRef::StringUtf8))),
1080 )],
1081 ..module("events")
1082 };
1083 let model = BindingModel::build(&api(vec![m]), "weaveffi");
1084 match &model.modules[0].functions[0].shape {
1085 CallShape::Iterator(it) => {
1086 assert_eq!(it.iter_tag, "weaveffi_events_GetMessagesIterator");
1087 assert_eq!(it.launch.symbol, "weaveffi_events_get_messages");
1088 assert_eq!(it.next.symbol, "weaveffi_events_GetMessagesIterator_next");
1089 assert_eq!(
1090 it.destroy_symbol,
1091 "weaveffi_events_GetMessagesIterator_destroy"
1092 );
1093 assert_eq!(it.next.ret, CType::Int32);
1094 let out_item = &it.next.params[1];
1096 assert_eq!(out_item.name, "out_item");
1097 assert_eq!(out_item.ty.render_c("weaveffi"), "const char**");
1098 }
1099 _ => panic!("expected iterator"),
1100 }
1101 }
1102
1103 #[test]
1104 fn struct_create_getters_and_builder() {
1105 let m = Module {
1106 interfaces: vec![],
1107 structs: vec![StructDef {
1108 name: "Contact".into(),
1109 doc: None,
1110 fields: vec![
1111 StructField {
1112 name: "name".into(),
1113 ty: TypeRef::StringUtf8,
1114 doc: None,
1115 default: None,
1116 },
1117 StructField {
1118 name: "age".into(),
1119 ty: TypeRef::I32,
1120 doc: None,
1121 default: None,
1122 },
1123 ],
1124 builder: true,
1125 }],
1126 ..module("contacts")
1127 };
1128 let model = BindingModel::build(&api(vec![m]), "weaveffi");
1129 let s = &model.modules[0].structs[0];
1130 assert_eq!(s.c_tag, "weaveffi_contacts_Contact");
1131 assert_eq!(s.create.symbol, "weaveffi_contacts_Contact_create");
1132 assert_eq!(s.destroy_symbol, "weaveffi_contacts_Contact_destroy");
1133 assert_eq!(
1134 s.fields[0].getter_symbol,
1135 "weaveffi_contacts_Contact_get_name"
1136 );
1137 let b = s.builder.as_ref().unwrap();
1138 assert_eq!(b.builder_tag, "weaveffi_contacts_ContactBuilder");
1139 assert_eq!(b.new_symbol, "weaveffi_contacts_Contact_Builder_new");
1140 assert_eq!(b.setters[0].1, "weaveffi_contacts_Contact_Builder_set_name");
1141 }
1142
1143 #[test]
1144 fn enum_constants_are_prefixed() {
1145 let m = Module {
1146 enums: vec![EnumDef {
1147 name: "Color".into(),
1148 doc: None,
1149 variants: vec![
1150 EnumVariant {
1151 name: "Red".into(),
1152 value: 0,
1153 doc: None,
1154 fields: vec![],
1155 },
1156 EnumVariant {
1157 name: "Green".into(),
1158 value: 1,
1159 doc: None,
1160 fields: vec![],
1161 },
1162 ],
1163 }],
1164 ..module("gfx")
1165 };
1166 let model = BindingModel::build(&api(vec![m]), "weaveffi");
1167 let e = &model.modules[0].enums[0];
1168 assert_eq!(e.c_tag, "weaveffi_gfx_Color");
1169 assert_eq!(e.variants[0].c_const, "weaveffi_gfx_Color_Red");
1170 assert_eq!(e.variants[1].c_const, "weaveffi_gfx_Color_Green");
1171 }
1172
1173 #[test]
1174 fn callbacks_and_listeners_are_linked() {
1175 let m = Module {
1176 callbacks: vec![CallbackDef {
1177 name: "on_message".into(),
1178 params: vec![param("text", TypeRef::StringUtf8)],
1179 doc: None,
1180 }],
1181 listeners: vec![ListenerDef {
1182 name: "messages".into(),
1183 event_callback: "on_message".into(),
1184 doc: None,
1185 }],
1186 ..module("events")
1187 };
1188 let model = BindingModel::build(&api(vec![m]), "weaveffi");
1189 let mb = &model.modules[0];
1190 let cb = &mb.callbacks[0];
1191 assert_eq!(cb.c_fn_type, "weaveffi_events_on_message_fn");
1192 assert_eq!(cb.abi_params.last().unwrap().name, "context");
1194 let l = &mb.listeners[0];
1195 assert_eq!(l.register_symbol, "weaveffi_events_register_messages");
1196 assert_eq!(l.unregister_symbol, "weaveffi_events_unregister_messages");
1197 assert_eq!(l.callback_c_fn_type, "weaveffi_events_on_message_fn");
1198 assert!(mb.callback("on_message").is_some());
1199 }
1200
1201 #[test]
1202 fn nested_modules_flatten_pre_order_with_paths() {
1203 let inner = Module {
1204 functions: vec![func("leaf_fn", vec![], None)],
1205 ..module("inner")
1206 };
1207 let outer = Module {
1208 functions: vec![func("outer_fn", vec![], None)],
1209 modules: vec![inner],
1210 ..module("outer")
1211 };
1212 let model = BindingModel::build(&api(vec![outer]), "weaveffi");
1213 let paths: Vec<&str> = model.modules.iter().map(|m| m.path.as_str()).collect();
1214 assert_eq!(paths, ["outer", "outer_inner"]);
1215 assert_eq!(
1216 model.modules[1].functions[0].c_base,
1217 "weaveffi_outer_inner_leaf_fn"
1218 );
1219 }
1220}