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