1use heck::ToUpperCamelCase;
25use weaveffi_ir::ir::{
26 Api, CallbackDef, EnumDef, Function, ListenerDef, Module, StructDef, TypeRef,
27};
28
29use crate::abi::{
30 self, async_callback_params, async_input_params, context_param, error_out_param, lower_param,
31 lower_return, sync_signature, AbiParam, CType,
32};
33
34#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct AbiFn {
38 pub symbol: String,
40 pub params: Vec<AbiParam>,
42 pub ret: CType,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum CallShape {
50 Sync(AbiFn),
52 Async(AsyncBinding),
54 Iterator(IteratorBinding),
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct AsyncBinding {
61 pub launch: AbiFn,
64 pub callback_type: String,
67 pub callback_params: Vec<AbiParam>,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct IteratorBinding {
75 pub elem: TypeRef,
77 pub iter_tag: String,
79 pub launch: AbiFn,
81 pub next: AbiFn,
83 pub destroy_symbol: String,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct ParamBinding {
90 pub name: String,
92 pub ty: TypeRef,
94 pub mutable: bool,
96 pub doc: Option<String>,
98 pub abi: Vec<AbiParam>,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct FnBinding {
105 pub name: String,
107 pub doc: Option<String>,
109 pub deprecated: Option<String>,
111 pub since: Option<String>,
113 pub cancellable: bool,
115 pub is_async: bool,
117 pub params: Vec<ParamBinding>,
119 pub ret: Option<TypeRef>,
122 pub c_base: String,
125 pub shape: CallShape,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct FieldBinding {
132 pub name: String,
134 pub doc: Option<String>,
136 pub ty: TypeRef,
138 pub getter_symbol: String,
141 pub getter_ret: CType,
143 pub getter_out_params: Vec<AbiParam>,
145 pub value_params: Vec<AbiParam>,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct BuilderBinding {
153 pub builder_tag: String,
155 pub new_symbol: String,
157 pub build_symbol: String,
159 pub destroy_symbol: String,
161 pub setters: Vec<(String, String)>,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct StructBinding {
169 pub name: String,
171 pub doc: Option<String>,
173 pub c_tag: String,
175 pub fields: Vec<FieldBinding>,
177 pub create: AbiFn,
179 pub destroy_symbol: String,
181 pub builder: Option<BuilderBinding>,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct EnumBinding {
195 pub name: String,
197 pub doc: Option<String>,
199 pub c_tag: String,
201 pub variants: Vec<EnumVariantBinding>,
204 pub rich: Option<RichEnumBinding>,
206}
207
208impl EnumBinding {
209 pub fn is_rich(&self) -> bool {
211 self.rich.is_some()
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
217pub struct EnumVariantBinding {
218 pub name: String,
220 pub value: i32,
222 pub doc: Option<String>,
224 pub c_const: String,
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct RichEnumBinding {
232 pub tag_symbol: String,
236 pub destroy_symbol: String,
238 pub variants: Vec<RichVariantBinding>,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
246pub struct RichVariantBinding {
247 pub name: String,
249 pub doc: Option<String>,
251 pub value: i32,
253 pub c_const: String,
255 pub create: AbiFn,
258 pub fields: Vec<FieldBinding>,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq)]
265pub struct CallbackBinding {
266 pub name: String,
268 pub doc: Option<String>,
270 pub c_fn_type: String,
272 pub params: Vec<ParamBinding>,
274 pub abi_params: Vec<AbiParam>,
276}
277
278#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct ListenerBinding {
281 pub name: String,
283 pub doc: Option<String>,
285 pub event_callback: String,
287 pub callback_c_fn_type: String,
289 pub register_symbol: String,
291 pub unregister_symbol: String,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
297pub struct ModuleBinding {
298 pub name: String,
300 pub segments: Vec<String>,
302 pub path: String,
304 pub doc: Option<String>,
306 pub enums: Vec<EnumBinding>,
308 pub structs: Vec<StructBinding>,
310 pub callbacks: Vec<CallbackBinding>,
312 pub listeners: Vec<ListenerBinding>,
314 pub functions: Vec<FnBinding>,
316}
317
318impl ModuleBinding {
319 pub fn callback(&self, name: &str) -> Option<&CallbackBinding> {
321 self.callbacks.iter().find(|c| c.name == name)
322 }
323
324 pub fn is_empty(&self) -> bool {
326 self.enums.is_empty()
327 && self.structs.is_empty()
328 && self.callbacks.is_empty()
329 && self.listeners.is_empty()
330 && self.functions.is_empty()
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
336pub struct BindingModel {
337 pub prefix: String,
339 pub version: String,
341 pub modules: Vec<ModuleBinding>,
343}
344
345impl BindingModel {
346 pub fn build(api: &Api, prefix: &str) -> Self {
351 let mut modules = Vec::new();
352 for m in &api.modules {
353 lower_module(m, &[], prefix, &mut modules);
354 }
355 Self {
356 prefix: prefix.to_string(),
357 version: api.version.clone(),
358 modules,
359 }
360 }
361
362 pub fn functions(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
364 self.modules
365 .iter()
366 .flat_map(|m| m.functions.iter().map(move |f| (m, f)))
367 }
368}
369
370fn lower_module(module: &Module, parent: &[String], prefix: &str, out: &mut Vec<ModuleBinding>) {
373 let mut segments = parent.to_vec();
374 segments.push(module.name.clone());
375 let path = segments.join("_");
376
377 let enums = module
378 .enums
379 .iter()
380 .map(|e| lower_enum(e, &path, prefix))
381 .collect();
382 let structs = module
383 .structs
384 .iter()
385 .map(|s| lower_struct(s, &path, prefix))
386 .collect();
387 let callbacks: Vec<CallbackBinding> = module
388 .callbacks
389 .iter()
390 .map(|c| lower_callback(c, &path, prefix))
391 .collect();
392 let listeners = module
393 .listeners
394 .iter()
395 .map(|l| lower_listener(l, &path, prefix))
396 .collect();
397 let functions = module
398 .functions
399 .iter()
400 .map(|f| lower_function(f, &path, prefix))
401 .collect();
402
403 let doc = module.functions.iter().find_map(|f| f.doc.clone());
406
407 out.push(ModuleBinding {
408 name: module.name.clone(),
409 segments: segments.clone(),
410 path,
411 doc,
412 enums,
413 structs,
414 callbacks,
415 listeners,
416 functions,
417 });
418
419 for child in &module.modules {
420 lower_module(child, &segments, prefix, out);
421 }
422}
423
424fn lower_param_binding(p: &weaveffi_ir::ir::Param, module: &str) -> ParamBinding {
425 ParamBinding {
426 name: p.name.clone(),
427 ty: p.ty.clone(),
428 mutable: p.mutable,
429 doc: p.doc.clone(),
430 abi: lower_param(&p.name, &p.ty, module, p.mutable),
431 }
432}
433
434fn lower_enum(e: &EnumDef, path: &str, prefix: &str) -> EnumBinding {
435 let c_tag = format!("{prefix}_{path}_{}", e.name);
436 let variants = e
437 .variants
438 .iter()
439 .map(|v| EnumVariantBinding {
440 name: v.name.clone(),
441 value: v.value,
442 doc: v.doc.clone(),
443 c_const: format!("{c_tag}_{}", v.name),
444 })
445 .collect();
446
447 let rich = e.is_rich().then(|| {
451 let variants = e
452 .variants
453 .iter()
454 .map(|v| {
455 let fields: Vec<FieldBinding> = v
456 .fields
457 .iter()
458 .map(|f| {
459 let r = lower_return(&f.ty, path);
460 FieldBinding {
461 name: f.name.clone(),
462 doc: f.doc.clone(),
463 ty: f.ty.clone(),
464 getter_symbol: format!("{c_tag}_{}_get_{}", v.name, f.name),
465 getter_ret: r.ret,
466 getter_out_params: r.out_params,
467 value_params: lower_param(&f.name, &f.ty, path, false),
468 }
469 })
470 .collect();
471 let mut create_params: Vec<AbiParam> = v
472 .fields
473 .iter()
474 .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
475 .collect();
476 create_params.push(error_out_param());
477 let create = AbiFn {
478 symbol: format!("{c_tag}_{}_new", v.name),
479 params: create_params,
480 ret: CType::ptr(CType::Named(format!("{path}_{}", e.name))),
481 };
482 RichVariantBinding {
483 name: v.name.clone(),
484 doc: v.doc.clone(),
485 value: v.value,
486 c_const: format!("{c_tag}_{}", v.name),
487 create,
488 fields,
489 }
490 })
491 .collect();
492 RichEnumBinding {
493 tag_symbol: format!("{c_tag}_tag"),
494 destroy_symbol: format!("{c_tag}_destroy"),
495 variants,
496 }
497 });
498
499 EnumBinding {
500 name: e.name.clone(),
501 doc: e.doc.clone(),
502 c_tag,
503 variants,
504 rich,
505 }
506}
507
508fn lower_struct(s: &StructDef, path: &str, prefix: &str) -> StructBinding {
509 let c_tag = format!("{prefix}_{path}_{}", s.name);
510
511 let fields: Vec<FieldBinding> = s
512 .fields
513 .iter()
514 .map(|f| {
515 let r = lower_return(&f.ty, path);
516 FieldBinding {
517 name: f.name.clone(),
518 doc: f.doc.clone(),
519 ty: f.ty.clone(),
520 getter_symbol: format!("{c_tag}_get_{}", f.name),
521 getter_ret: r.ret,
522 getter_out_params: r.out_params,
523 value_params: lower_param(&f.name, &f.ty, path, false),
524 }
525 })
526 .collect();
527
528 let mut create_params: Vec<AbiParam> = s
530 .fields
531 .iter()
532 .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
533 .collect();
534 create_params.push(error_out_param());
535 let create = AbiFn {
536 symbol: format!("{c_tag}_create"),
537 params: create_params,
538 ret: CType::ptr(CType::Named(format!("{path}_{}", s.name))),
539 };
540
541 let builder = s.builder.then(|| {
542 let builder_tag = format!("{c_tag}Builder");
543 let setters = s
544 .fields
545 .iter()
546 .map(|f| (f.name.clone(), format!("{c_tag}_Builder_set_{}", f.name)))
547 .collect();
548 BuilderBinding {
549 builder_tag,
550 new_symbol: format!("{c_tag}_Builder_new"),
551 build_symbol: format!("{c_tag}_Builder_build"),
552 destroy_symbol: format!("{c_tag}_Builder_destroy"),
553 setters,
554 }
555 });
556
557 StructBinding {
558 name: s.name.clone(),
559 doc: s.doc.clone(),
560 c_tag: c_tag.clone(),
561 fields,
562 create,
563 destroy_symbol: format!("{c_tag}_destroy"),
564 builder,
565 }
566}
567
568fn lower_callback(c: &CallbackDef, path: &str, prefix: &str) -> CallbackBinding {
569 let params: Vec<ParamBinding> = c
570 .params
571 .iter()
572 .map(|p| lower_param_binding(p, path))
573 .collect();
574 let mut abi_params: Vec<AbiParam> = params.iter().flat_map(|p| p.abi.clone()).collect();
575 abi_params.push(context_param());
576 CallbackBinding {
577 name: c.name.clone(),
578 doc: c.doc.clone(),
579 c_fn_type: format!("{prefix}_{path}_{}_fn", c.name),
580 params,
581 abi_params,
582 }
583}
584
585fn lower_listener(l: &ListenerDef, path: &str, prefix: &str) -> ListenerBinding {
586 ListenerBinding {
587 name: l.name.clone(),
588 doc: l.doc.clone(),
589 event_callback: l.event_callback.clone(),
590 callback_c_fn_type: format!("{prefix}_{path}_{}_fn", l.event_callback),
591 register_symbol: format!("{prefix}_{path}_register_{}", l.name),
592 unregister_symbol: format!("{prefix}_{path}_unregister_{}", l.name),
593 }
594}
595
596fn lower_function(f: &Function, path: &str, prefix: &str) -> FnBinding {
597 let params: Vec<ParamBinding> = f
598 .params
599 .iter()
600 .map(|p| lower_param_binding(p, path))
601 .collect();
602 let c_base = format!("{prefix}_{path}_{}", f.name);
603
604 let shape = if let Some(TypeRef::Iterator(inner)) = &f.returns {
605 let pascal = f.name.to_upper_camel_case();
606 let iter_tag = format!("{prefix}_{path}_{pascal}Iterator");
607 let iter_core = format!("{path}_{pascal}Iterator");
608
609 let mut launch_params: Vec<AbiParam> = f
611 .params
612 .iter()
613 .flat_map(|p| lower_param(&p.name, &p.ty, path, p.mutable))
614 .collect();
615 launch_params.push(error_out_param());
616 let launch = AbiFn {
617 symbol: c_base.clone(),
618 params: launch_params,
619 ret: CType::ptr(CType::Named(iter_core.clone())),
620 };
621
622 let item = lower_return(inner, path);
624 let mut next_params = vec![
625 AbiParam::new("iter", CType::ptr(CType::Named(iter_core.clone()))),
626 AbiParam::new("out_item", CType::ptr(item.ret)),
627 ];
628 next_params.extend(item.out_params);
629 next_params.push(error_out_param());
630 let next = AbiFn {
631 symbol: format!("{iter_tag}_next"),
632 params: next_params,
633 ret: CType::Int32,
634 };
635
636 CallShape::Iterator(IteratorBinding {
637 elem: (**inner).clone(),
638 iter_tag: iter_tag.clone(),
639 launch,
640 next,
641 destroy_symbol: format!("{iter_tag}_destroy"),
642 })
643 } else if f.r#async {
644 let callback_type = format!("{c_base}_callback");
645 let mut launch_params = async_input_params(f, path);
646 launch_params.push(AbiParam::new(
647 "callback",
648 CType::Named(format!("{path}_{}_callback", f.name)),
649 ));
650 launch_params.push(context_param());
651 let launch = AbiFn {
652 symbol: format!("{c_base}_async"),
653 params: launch_params,
654 ret: CType::Void,
655 };
656 CallShape::Async(AsyncBinding {
657 launch,
658 callback_type,
659 callback_params: async_callback_params(f.returns.as_ref(), path),
660 })
661 } else {
662 let sig = sync_signature(&f.params, f.returns.as_ref(), path);
663 CallShape::Sync(AbiFn {
664 symbol: c_base.clone(),
665 params: sig.params,
666 ret: sig.ret,
667 })
668 };
669
670 FnBinding {
671 name: f.name.clone(),
672 doc: f.doc.clone(),
673 deprecated: f.deprecated.clone(),
674 since: f.since.clone(),
675 cancellable: f.cancellable,
676 is_async: f.r#async,
677 params,
678 ret: f.returns.clone(),
679 c_base,
680 shape,
681 }
682}
683
684pub fn iterator_item_ctype(elem: &TypeRef, module: &str) -> CType {
687 abi::lower_return(elem, module).ret
688}
689
690#[cfg(test)]
691mod tests {
692 use super::*;
693 use weaveffi_ir::ir::{
694 CallbackDef, EnumDef, EnumVariant, Function, ListenerDef, Module, Param, StructDef,
695 StructField,
696 };
697
698 fn param(name: &str, ty: TypeRef) -> Param {
699 Param {
700 name: name.into(),
701 ty,
702 mutable: false,
703 doc: None,
704 }
705 }
706
707 fn func(name: &str, params: Vec<Param>, returns: Option<TypeRef>) -> Function {
708 Function {
709 name: name.into(),
710 params,
711 returns,
712 doc: None,
713 r#async: false,
714 cancellable: false,
715 deprecated: None,
716 since: None,
717 }
718 }
719
720 fn module(name: &str) -> Module {
721 Module {
722 name: name.into(),
723 functions: vec![],
724 structs: vec![],
725 enums: vec![],
726 callbacks: vec![],
727 listeners: vec![],
728 errors: None,
729 modules: vec![],
730 }
731 }
732
733 fn api(modules: Vec<Module>) -> Api {
734 Api {
735 version: "0.4.0".into(),
736 modules,
737 generators: None,
738 package: None,
739 }
740 }
741
742 #[test]
743 fn sync_function_symbol_and_sig() {
744 let m = Module {
745 functions: vec![func(
746 "add",
747 vec![param("a", TypeRef::I32), param("b", TypeRef::I32)],
748 Some(TypeRef::I32),
749 )],
750 ..module("math")
751 };
752 let model = BindingModel::build(&api(vec![m]), "weaveffi");
753 let f = &model.modules[0].functions[0];
754 assert_eq!(f.c_base, "weaveffi_math_add");
755 match &f.shape {
756 CallShape::Sync(abi) => {
757 assert_eq!(abi.symbol, "weaveffi_math_add");
758 assert_eq!(abi.ret, CType::Int32);
759 let rendered: Vec<String> = abi
760 .params
761 .iter()
762 .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
763 .collect();
764 assert_eq!(
765 rendered,
766 ["int32_t a", "int32_t b", "weaveffi_error* out_err"]
767 );
768 }
769 _ => panic!("expected sync"),
770 }
771 }
772
773 #[test]
774 fn prefix_is_honored_everywhere() {
775 let m = Module {
776 functions: vec![func("ping", vec![], None)],
777 ..module("net")
778 };
779 let model = BindingModel::build(&api(vec![m]), "acme");
780 let f = &model.modules[0].functions[0];
781 assert_eq!(f.c_base, "acme_net_ping");
782 }
783
784 #[test]
785 fn async_function_has_launch_and_callback() {
786 let m = Module {
787 functions: vec![Function {
788 cancellable: true,
789 r#async: true,
790 ..func(
791 "fetch",
792 vec![param("id", TypeRef::I64)],
793 Some(TypeRef::StringUtf8),
794 )
795 }],
796 ..module("net")
797 };
798 let model = BindingModel::build(&api(vec![m]), "weaveffi");
799 match &model.modules[0].functions[0].shape {
800 CallShape::Async(a) => {
801 assert_eq!(a.launch.symbol, "weaveffi_net_fetch_async");
802 assert_eq!(a.callback_type, "weaveffi_net_fetch_callback");
803 let last_two: Vec<&str> = a
804 .launch
805 .params
806 .iter()
807 .rev()
808 .take(2)
809 .map(|p| p.name.as_str())
810 .collect();
811 assert_eq!(last_two, ["context", "callback"]);
812 assert!(a.launch.params.iter().any(|p| p.name == "cancel_token"));
814 assert_eq!(a.callback_params[0].name, "context");
816 assert_eq!(a.callback_params[1].name, "err");
817 }
818 _ => panic!("expected async"),
819 }
820 }
821
822 #[test]
823 fn iterator_function_has_next_and_destroy() {
824 let m = Module {
825 functions: vec![func(
826 "get_messages",
827 vec![],
828 Some(TypeRef::Iterator(Box::new(TypeRef::StringUtf8))),
829 )],
830 ..module("events")
831 };
832 let model = BindingModel::build(&api(vec![m]), "weaveffi");
833 match &model.modules[0].functions[0].shape {
834 CallShape::Iterator(it) => {
835 assert_eq!(it.iter_tag, "weaveffi_events_GetMessagesIterator");
836 assert_eq!(it.launch.symbol, "weaveffi_events_get_messages");
837 assert_eq!(it.next.symbol, "weaveffi_events_GetMessagesIterator_next");
838 assert_eq!(
839 it.destroy_symbol,
840 "weaveffi_events_GetMessagesIterator_destroy"
841 );
842 assert_eq!(it.next.ret, CType::Int32);
843 let out_item = &it.next.params[1];
845 assert_eq!(out_item.name, "out_item");
846 assert_eq!(out_item.ty.render_c("weaveffi"), "const char**");
847 }
848 _ => panic!("expected iterator"),
849 }
850 }
851
852 #[test]
853 fn struct_create_getters_and_builder() {
854 let m = Module {
855 structs: vec![StructDef {
856 name: "Contact".into(),
857 doc: None,
858 fields: vec![
859 StructField {
860 name: "name".into(),
861 ty: TypeRef::StringUtf8,
862 doc: None,
863 default: None,
864 },
865 StructField {
866 name: "age".into(),
867 ty: TypeRef::I32,
868 doc: None,
869 default: None,
870 },
871 ],
872 builder: true,
873 }],
874 ..module("contacts")
875 };
876 let model = BindingModel::build(&api(vec![m]), "weaveffi");
877 let s = &model.modules[0].structs[0];
878 assert_eq!(s.c_tag, "weaveffi_contacts_Contact");
879 assert_eq!(s.create.symbol, "weaveffi_contacts_Contact_create");
880 assert_eq!(s.destroy_symbol, "weaveffi_contacts_Contact_destroy");
881 assert_eq!(
882 s.fields[0].getter_symbol,
883 "weaveffi_contacts_Contact_get_name"
884 );
885 let b = s.builder.as_ref().unwrap();
886 assert_eq!(b.builder_tag, "weaveffi_contacts_ContactBuilder");
887 assert_eq!(b.new_symbol, "weaveffi_contacts_Contact_Builder_new");
888 assert_eq!(b.setters[0].1, "weaveffi_contacts_Contact_Builder_set_name");
889 }
890
891 #[test]
892 fn enum_constants_are_prefixed() {
893 let m = Module {
894 enums: vec![EnumDef {
895 name: "Color".into(),
896 doc: None,
897 variants: vec![
898 EnumVariant {
899 name: "Red".into(),
900 value: 0,
901 doc: None,
902 fields: vec![],
903 },
904 EnumVariant {
905 name: "Green".into(),
906 value: 1,
907 doc: None,
908 fields: vec![],
909 },
910 ],
911 }],
912 ..module("gfx")
913 };
914 let model = BindingModel::build(&api(vec![m]), "weaveffi");
915 let e = &model.modules[0].enums[0];
916 assert_eq!(e.c_tag, "weaveffi_gfx_Color");
917 assert_eq!(e.variants[0].c_const, "weaveffi_gfx_Color_Red");
918 assert_eq!(e.variants[1].c_const, "weaveffi_gfx_Color_Green");
919 }
920
921 #[test]
922 fn callbacks_and_listeners_are_linked() {
923 let m = Module {
924 callbacks: vec![CallbackDef {
925 name: "on_message".into(),
926 params: vec![param("text", TypeRef::StringUtf8)],
927 doc: None,
928 }],
929 listeners: vec![ListenerDef {
930 name: "messages".into(),
931 event_callback: "on_message".into(),
932 doc: None,
933 }],
934 ..module("events")
935 };
936 let model = BindingModel::build(&api(vec![m]), "weaveffi");
937 let mb = &model.modules[0];
938 let cb = &mb.callbacks[0];
939 assert_eq!(cb.c_fn_type, "weaveffi_events_on_message_fn");
940 assert_eq!(cb.abi_params.last().unwrap().name, "context");
942 let l = &mb.listeners[0];
943 assert_eq!(l.register_symbol, "weaveffi_events_register_messages");
944 assert_eq!(l.unregister_symbol, "weaveffi_events_unregister_messages");
945 assert_eq!(l.callback_c_fn_type, "weaveffi_events_on_message_fn");
946 assert!(mb.callback("on_message").is_some());
947 }
948
949 #[test]
950 fn nested_modules_flatten_pre_order_with_paths() {
951 let inner = Module {
952 functions: vec![func("leaf_fn", vec![], None)],
953 ..module("inner")
954 };
955 let outer = Module {
956 functions: vec![func("outer_fn", vec![], None)],
957 modules: vec![inner],
958 ..module("outer")
959 };
960 let model = BindingModel::build(&api(vec![outer]), "weaveffi");
961 let paths: Vec<&str> = model.modules.iter().map(|m| m.path.as_str()).collect();
962 assert_eq!(paths, ["outer", "outer_inner"]);
963 assert_eq!(
964 model.modules[1].functions[0].c_base,
965 "weaveffi_outer_inner_leaf_fn"
966 );
967 }
968}