1use super::*;
2
3#[derive(Debug, Clone)]
5pub struct EmitOptions {
6 pub assert_no_leaks_at_run_main: bool,
10 pub function_lines: BTreeMap<String, usize>,
12}
13
14impl Default for EmitOptions {
15 fn default() -> Self {
16 Self {
17 assert_no_leaks_at_run_main: true,
18 function_lines: BTreeMap::new(),
19 }
20 }
21}
22
23pub fn emit_program_best_effort(program: &ruka_mir::MirProgram) -> WasmArtifacts {
29 emit_program_best_effort_with_options(program, EmitOptions::default())
30}
31
32pub fn emit_program_best_effort_with_options(
34 program: &ruka_mir::MirProgram,
35 options: EmitOptions,
36) -> WasmArtifacts {
37 let mut diagnostics = Vec::<WasmDiagnostic>::new();
38
39 if let Err(error) = validate_owned_function_returns(program) {
40 diagnostics.push(WasmDiagnostic {
41 phase: "codegen_wasm",
42 message: error.to_string(),
43 });
44 return WasmArtifacts {
45 wat_source: String::new(),
46 wasm_bytes: None,
47 diagnostics,
48 };
49 }
50
51 for (_, function) in program.functions.iter() {
52 function.assert_valid();
53 }
54
55 let string_literals = collect_string_literals(program).0;
56 let function_order = program
57 .functions
58 .iter()
59 .map(|(id, function)| (id, function.name.clone()))
60 .collect::<Vec<_>>();
61 let callee_param_runtime_mask = program
62 .functions
63 .iter()
64 .map(|(id, function)| {
65 let mask = function
66 .params
67 .iter()
68 .map(|local| !matches!(function.locals[*local].ty, Ty::Unit))
69 .collect::<Vec<_>>();
70 (id.as_u32(), mask)
71 })
72 .collect::<BTreeMap<u32, Vec<bool>>>();
73 let callee_returns_runtime = program
74 .functions
75 .iter()
76 .map(|(id, function)| {
77 (
78 id.as_u32(),
79 !matches!(function.return_ty, Ty::Unit)
80 && !function_returns_via_out_slot(&function.return_ty),
81 )
82 })
83 .collect::<BTreeMap<u32, bool>>();
84 let callee_param_inout_mask = program
85 .functions
86 .iter()
87 .map(|(id, function)| {
88 let mask = function
89 .param_bindings()
90 .map(borrowed_param_uses_inout)
91 .collect::<Vec<_>>();
92 (id.as_u32(), mask)
93 })
94 .collect::<BTreeMap<u32, Vec<bool>>>();
95 let callee_returns_via_out_slot = program
96 .functions
97 .iter()
98 .map(|(id, function)| {
99 (
100 id.as_u32(),
101 function_returns_via_out_slot(&function.return_ty),
102 )
103 })
104 .collect::<BTreeMap<u32, bool>>();
105 let callee_param_runtime_types = program
106 .functions
107 .iter()
108 .map(|(id, function)| {
109 let types = function
110 .param_bindings()
111 .filter_map(|binding| {
112 if matches!(binding.local.ty, Ty::Unit) {
113 None
114 } else {
115 Some(if borrowed_param_uses_inout(binding) {
116 ty_to_valtype(binding.semantic_ty())
117 } else {
118 param_binding_valtype(binding)
119 })
120 }
121 })
122 .collect::<Vec<_>>();
123 (id.as_u32(), types)
124 })
125 .collect::<BTreeMap<u32, Vec<ValType>>>();
126
127 let linked = match link_runtime_with_literals(&string_literals) {
128 Ok(linked) => linked,
129 Err(error) => {
130 diagnostics.push(WasmDiagnostic {
131 phase: "codegen_wasm",
132 message: error.to_string(),
133 });
134 return WasmArtifacts {
135 wat_source: String::new(),
136 wasm_bytes: None,
137 diagnostics,
138 };
139 }
140 };
141 let mut module = linked.module;
142 let mut used_function_names = module
143 .funcs
144 .iter()
145 .filter_map(|item| item.name.clone())
146 .collect::<BTreeSet<_>>();
147 let runtime = linked.runtime;
148 let memory_id = linked.memory_id;
149 let string_literal_offsets = linked.string_literal_offsets;
150 let pointer_drop_functions = emit_pointer_drop_functions(
151 &mut module,
152 &runtime,
153 memory_id,
154 &program.structs,
155 &program.enums,
156 program,
157 )
158 .map_err(|error| WasmDiagnostic {
159 phase: "codegen_wasm",
160 message: error.to_string(),
161 })
162 .map_or_else(
163 |diagnostic| {
164 diagnostics.push(diagnostic);
165 BTreeMap::<String, FunctionId>::new()
166 },
167 |functions| functions,
168 );
169
170 let mut func_id_by_mir = BTreeMap::<u32, FunctionId>::new();
171 let mut main_function_id = None::<FunctionId>;
172 let mut main_returns_runtime_value = false;
173 for (func_id, _) in &function_order {
174 let function_ref = &program.functions[*func_id];
175 let (params, results) = signature_types(function_ref);
176 let mut builder = FunctionBuilder::new(&mut module.types, ¶ms, &results);
177 if let Some(result_ty) = results.first().copied() {
178 match result_ty {
179 ValType::I32 => {
180 builder.func_body().i32_const(0);
181 }
182 ValType::I64 => {
183 builder.func_body().i64_const(0);
184 }
185 _ => {}
186 }
187 }
188
189 let mut args = Vec::<LocalId>::new();
190 for param_ty in ¶ms {
191 args.push(module.locals.add(*param_ty));
192 }
193
194 let function_id = builder.finish(args, &mut module.funcs);
195 let readable_name = format!("rk::{}", readable_symbol_fragment(&function_ref.name));
196 assign_unique_function_name(
197 &mut module,
198 &mut used_function_names,
199 function_id,
200 &readable_name,
201 );
202 if function_ref.name == "main" {
203 main_function_id = Some(function_id);
204 main_returns_runtime_value = !matches!(function_ref.return_ty, Ty::Unit)
205 && !function_returns_via_out_slot(&function_ref.return_ty);
206 }
207 let _ = func_id_by_mir.insert(func_id.as_u32(), function_id);
208 }
209
210 if let Some(main_function_id) = main_function_id {
211 module.exports.add("main", main_function_id);
212 let mut builder = FunctionBuilder::new(&mut module.types, &[], &[]);
213 let mut body = builder.func_body();
214 body.call(main_function_id);
215 if main_returns_runtime_value {
216 body.drop();
217 }
218 if options.assert_no_leaks_at_run_main {
219 let assert_no_leaks = runtime_function(&runtime, wasm_api::RT_ASSERT_NO_LEAKS_SYMBOL)
220 .map_err(|error| WasmDiagnostic {
221 phase: "codegen_wasm",
222 message: error.to_string(),
223 })
224 .map_or_else(
225 |diagnostic| {
226 diagnostics.push(diagnostic);
227 None
228 },
229 |function| Some(function.function_id),
230 );
231 if let Some(assert_no_leaks) = assert_no_leaks {
232 body.call(assert_no_leaks);
233 }
234 }
235 let run_main_id = builder.finish(Vec::new(), &mut module.funcs);
236 assign_unique_function_name(
237 &mut module,
238 &mut used_function_names,
239 run_main_id,
240 "rk::run_main",
241 );
242 module.exports.add("run_main", run_main_id);
243 }
244
245 for (type_key, function_id) in &pointer_drop_functions {
246 let readable_name = format!("rk::drop::{}", readable_symbol_fragment(type_key));
247 assign_unique_function_name(
248 &mut module,
249 &mut used_function_names,
250 *function_id,
251 &readable_name,
252 );
253 }
254
255 for (func_id, _) in &function_order {
256 let function_ref = &program.functions[*func_id];
257 let Some(wasm_func_id) = func_id_by_mir.get(&func_id.as_u32()).copied() else {
258 diagnostics.push(WasmDiagnostic {
259 phase: "codegen_wasm",
260 message: LowerError::UnknownCallee.to_string(),
261 });
262 return WasmArtifacts {
263 wat_source: String::new(),
264 wasm_bytes: None,
265 diagnostics,
266 };
267 };
268 let lower_ctx = ModuleLowerCtx {
269 func_id_by_mir: &func_id_by_mir,
270 callee_param_runtime_mask: &callee_param_runtime_mask,
271 callee_param_runtime_types: &callee_param_runtime_types,
272 callee_param_inout_mask: &callee_param_inout_mask,
273 callee_returns_via_out_slot: &callee_returns_via_out_slot,
274 callee_returns_runtime: &callee_returns_runtime,
275 runtime: &runtime,
276 pointer_drop_functions: &pointer_drop_functions,
277 memory_id,
278 string_literal_offsets: &string_literal_offsets,
279 structs: &program.structs,
280 enums: &program.enums,
281 function_lines: &options.function_lines,
282 };
283 match lower_function(&mut module, wasm_func_id, function_ref, &lower_ctx) {
284 Ok(()) => {}
285 Err(error) => {
286 diagnostics.push(WasmDiagnostic {
287 phase: "codegen_wasm",
288 message: format!("function `{}`: {error}", function_ref.name),
289 });
290 }
291 }
292 }
293
294 let wasm_bytes = if diagnostics.is_empty() {
295 Some(module.emit_wasm())
296 } else {
297 None
298 };
299
300 let wat_source = match &wasm_bytes {
301 Some(bytes) => {
302 let mut validator = Validator::new();
303 if let Err(error) = validator.validate_all(bytes) {
304 diagnostics.push(WasmDiagnostic {
305 phase: "codegen_wasm",
306 message: format!("emitted wasm failed validation: {error}"),
307 });
308 String::new()
309 } else {
310 let mut wat = String::new();
311 let mut printer = WatPrinterConfig::new();
312 printer.name_unnamed(true);
313 match printer.print(bytes, &mut PrintFmtWrite(&mut wat)) {
314 Ok(()) => wat,
315 Err(error) => {
316 diagnostics.push(WasmDiagnostic {
317 phase: "codegen_wasm",
318 message: format!("failed to render WAT from emitted WASM: {error}"),
319 });
320 String::new()
321 }
322 }
323 }
324 }
325 None => String::new(),
326 };
327
328 let wasm_bytes = if diagnostics.is_empty() {
329 wasm_bytes
330 } else {
331 None
332 };
333
334 WasmArtifacts {
335 wat_source,
336 wasm_bytes,
337 diagnostics,
338 }
339}
340
341fn validate_owned_function_returns(program: &ruka_mir::MirProgram) -> Result<(), LowerError> {
343 for (_, function) in program.functions.iter() {
344 if is_borrowed_return_ty(&function.return_ty) {
345 return Err(LowerError::BorrowedReturnType {
346 function: function.name.clone(),
347 return_ty: function.return_ty.to_string(),
348 });
349 }
350 }
351 Ok(())
352}
353
354fn is_borrowed_return_ty(return_ty: &Ty) -> bool {
356 matches!(return_ty, Ty::RefRo(_) | Ty::RefMut(_))
357}
358
359pub(crate) fn signature_types(function: &ruka_mir::MirFunction) -> (Vec<ValType>, Vec<ValType>) {
361 let mut params = function
362 .param_bindings()
363 .filter_map(|binding| {
364 if matches!(binding.local.ty, Ty::Unit) {
365 None
366 } else {
367 Some(if borrowed_param_uses_inout(binding) {
368 ty_to_valtype(binding.semantic_ty())
369 } else {
370 param_binding_valtype(binding)
371 })
372 }
373 })
374 .collect::<Vec<_>>();
375 if function_returns_via_out_slot(&function.return_ty) {
376 params.insert(0, ValType::I32);
377 }
378 let mut results = function
379 .param_bindings()
380 .filter_map(|binding| {
381 if borrowed_param_uses_inout(binding) {
382 Some(ty_to_valtype(binding.semantic_ty()))
383 } else {
384 None
385 }
386 })
387 .collect::<Vec<_>>();
388 if !matches!(function.return_ty, Ty::Unit)
389 && !function_returns_via_out_slot(&function.return_ty)
390 {
391 results.push(ty_to_valtype(&function.return_ty));
392 }
393 (params, results)
394}
395
396pub(crate) fn borrowed_param_uses_inout(binding: ruka_mir::MirParamBinding<'_>) -> bool {
397 binding.expects_mut_borrow() && !is_shadow_stack_aggregate_ty(binding.semantic_ty())
398}
399
400pub(crate) fn function_returns_via_out_slot(return_ty: &Ty) -> bool {
401 is_shadow_stack_aggregate_ty(return_ty)
402}
403
404pub(crate) fn param_binding_valtype(binding: ruka_mir::MirParamBinding<'_>) -> ValType {
406 if binding.expects_mut_borrow() {
407 ty_to_valtype(&binding.local.ty)
408 } else {
409 ty_to_valtype(binding.semantic_ty())
410 }
411}
412
413pub(crate) fn ty_to_valtype(ty: &Ty) -> ValType {
415 match ty {
416 Ty::U8 | Ty::U16 | Ty::U32 | Ty::I8 | Ty::I16 | Ty::I32 | Ty::Bool => ValType::I32,
417 Ty::U64 | Ty::I64 => ValType::I64,
418 Ty::F32 => ValType::F32,
419 Ty::F64 => ValType::F64,
420 Ty::String
421 | Ty::Unit
422 | Ty::Option(_)
423 | Ty::Pointer(_)
424 | Ty::Array { .. }
425 | Ty::Tuple(_)
426 | Ty::Struct { .. }
427 | Ty::Enum { .. } => ValType::I32,
428 Ty::Slice(_) => ValType::I32,
429 Ty::RefRo(_) | Ty::RefMut(_) => ValType::I32,
430 }
431}
432
433pub(crate) fn is_shadow_stack_aggregate_ty(ty: &Ty) -> bool {
434 matches!(ty, Ty::Tuple(_) | Ty::Struct { .. } | Ty::Slice(_))
435}
436
437pub(crate) fn align_up_u32(value: u32, alignment: u32) -> u32 {
438 if alignment == 0 {
439 return value;
440 }
441 let mask = alignment - 1;
442 (value + mask) & !mask
443}
444
445pub(crate) fn should_shadow_stack_local(info: &ruka_mir::LocalInfo) -> bool {
446 if !info.repr.is_place() {
447 return is_shadow_stack_aggregate_ty(&info.ty);
448 }
449 matches!(
450 &info.ty,
451 Ty::RefRo(inner) | Ty::RefMut(inner) if matches!(inner.as_ref(), Ty::Slice(_))
452 )
453}
454
455pub(crate) fn ty_key(ty: &Ty) -> String {
456 format!("{ty:?}")
457}
458
459pub(crate) fn readable_symbol_fragment(value: &str) -> String {
460 let mut out = String::with_capacity(value.len());
461 let mut last_was_sep = false;
462 for ch in value.chars() {
463 let is_kept = ch.is_ascii_alphanumeric()
464 || matches!(
465 ch,
466 '_' | ':' | '<' | '>' | '[' | ']' | ',' | '.' | '*' | '&' | '-'
467 );
468 let next = if is_kept { ch } else { '_' };
469 if next == '_' {
470 if last_was_sep {
471 continue;
472 }
473 last_was_sep = true;
474 } else {
475 last_was_sep = false;
476 }
477 out.push(next);
478 }
479 let trimmed = out.trim_matches('_');
480 if trimmed.is_empty() {
481 "anon".to_owned()
482 } else {
483 trimmed.to_owned()
484 }
485}
486
487pub(crate) fn assign_unique_function_name(
488 module: &mut walrus::Module,
489 used_names: &mut BTreeSet<String>,
490 function_id: FunctionId,
491 preferred_name: &str,
492) {
493 let mut candidate = preferred_name.to_owned();
494 let mut suffix = 1_u32;
495 while used_names.contains(&candidate) {
496 suffix += 1;
497 candidate = format!("{preferred_name}::{suffix}");
498 }
499 let _ = used_names.insert(candidate.clone());
500 module.funcs.get_mut(function_id).name = Some(candidate);
501}
502
503pub(crate) fn enum_decl_by_name<'a>(
504 enums: &'a [ruka_mir::MirEnumDecl],
505 enum_name: &str,
506) -> Result<&'a ruka_mir::MirEnumDecl, LowerError> {
507 enums
508 .iter()
509 .find(|decl| decl.name == enum_name)
510 .ok_or(LowerError::UnsupportedInstruction(
511 "missing enum declaration",
512 ))
513}
514
515pub(crate) fn enum_variant_index(
516 enums: &[ruka_mir::MirEnumDecl],
517 enum_name: &str,
518 variant: &str,
519) -> Result<u32, LowerError> {
520 let decl = enum_decl_by_name(enums, enum_name)?;
521 let index = decl
522 .variants
523 .iter()
524 .position(|item| item.name == variant)
525 .ok_or(LowerError::UnsupportedInstruction("unknown enum variant"))?;
526 u32::try_from(index)
527 .map_err(|_| LowerError::UnsupportedInstruction("enum variant index overflow"))
528}
529
530pub(crate) fn mir_type_expr_to_ty_with_subst(
531 ty: &ruka_mir::MirTypeExpr,
532 bindings: &BTreeMap<String, Ty>,
533) -> Result<Ty, LowerError> {
534 match ty {
535 ruka_mir::MirTypeExpr::Named(name) => match name.as_str() {
536 "Unit" => Ok(Ty::Unit),
537 "u8" => Ok(Ty::U8),
538 "u16" => Ok(Ty::U16),
539 "u32" => Ok(Ty::U32),
540 "u64" => Ok(Ty::U64),
541 "i8" => Ok(Ty::I8),
542 "i16" => Ok(Ty::I16),
543 "i32" => Ok(Ty::I32),
544 "i64" => Ok(Ty::I64),
545 "f32" => Ok(Ty::F32),
546 "f64" => Ok(Ty::F64),
547 "String" => Ok(Ty::String),
548 "Bool" => Ok(Ty::Bool),
549 _ => {
550 if let Some(actual) = bindings.get(name) {
551 Ok(actual.clone())
552 } else {
553 Ok(Ty::Struct {
554 name: name.to_owned(),
555 args: Vec::new(),
556 })
557 }
558 }
559 },
560 ruka_mir::MirTypeExpr::Pointer { item } => Ok(Ty::Pointer(Box::new(
561 mir_type_expr_to_ty_with_subst(item, bindings)?,
562 ))),
563 ruka_mir::MirTypeExpr::Array { item, len } => Ok(Ty::Array {
564 item: Box::new(mir_type_expr_to_ty_with_subst(item, bindings)?),
565 len: *len,
566 }),
567 ruka_mir::MirTypeExpr::Slice { item } => Ok(Ty::Slice(Box::new(
568 mir_type_expr_to_ty_with_subst(item, bindings)?,
569 ))),
570 ruka_mir::MirTypeExpr::Tuple(items) => Ok(Ty::Tuple(
571 items
572 .iter()
573 .map(|item| mir_type_expr_to_ty_with_subst(item, bindings))
574 .collect::<Result<Vec<_>, _>>()?,
575 )),
576 ruka_mir::MirTypeExpr::Apply {
577 callee,
578 args: type_args,
579 } => Ok(Ty::Struct {
580 name: callee.clone(),
581 args: type_args
582 .iter()
583 .map(|item| mir_type_expr_to_ty_with_subst(item, bindings))
584 .collect::<Result<Vec<_>, _>>()?,
585 }),
586 }
587}
588
589pub(crate) fn collect_pointer_types(
590 ty: &Ty,
591 structs: &[ruka_mir::MirStructDecl],
592 enums: &[ruka_mir::MirEnumDecl],
593 out: &mut BTreeMap<String, Ty>,
594 visiting_structs: &mut BTreeSet<String>,
595) {
596 match ty {
597 Ty::Pointer(item) => {
598 let pointer_ty = Ty::Pointer(item.clone());
599 let _ = out.insert(ty_key(&pointer_ty), pointer_ty);
600 collect_pointer_types(item, structs, enums, out, visiting_structs);
601 }
602 Ty::Option(item) => {
603 collect_pointer_types(item, structs, enums, out, visiting_structs);
604 }
605 Ty::Array { item, .. } | Ty::Slice(item) | Ty::RefRo(item) | Ty::RefMut(item) => {
606 collect_pointer_types(item, structs, enums, out, visiting_structs)
607 }
608 Ty::Tuple(items) => {
609 for item in items {
610 collect_pointer_types(item, structs, enums, out, visiting_structs);
611 }
612 }
613 Ty::Struct { name, .. } => {
614 if !visiting_structs.insert(name.clone()) {
615 return;
616 }
617 if let Some(decl) = structs.iter().find(|decl| decl.name == *name) {
618 for field in &decl.fields {
619 if let Ok(field_ty) =
620 aggregate::aggregate_field_ty(ty, field.name.as_str(), structs, enums)
621 {
622 collect_pointer_types(&field_ty, structs, enums, out, visiting_structs);
623 }
624 }
625 }
626 let _ = visiting_structs.remove(name);
627 }
628 Ty::Enum { name, args } => {
629 if let Some(decl) = enums.iter().find(|decl| decl.name == *name) {
630 let bindings = decl
631 .type_params
632 .iter()
633 .cloned()
634 .zip(args.iter().cloned())
635 .collect::<BTreeMap<_, _>>();
636 for variant in &decl.variants {
637 for payload in &variant.payload {
638 if let Ok(payload_ty) = mir_type_expr_to_ty_with_subst(payload, &bindings) {
639 collect_pointer_types(
640 &payload_ty,
641 structs,
642 enums,
643 out,
644 visiting_structs,
645 );
646 }
647 }
648 }
649 }
650 for arg in args {
651 collect_pointer_types(arg, structs, enums, out, visiting_structs);
652 }
653 }
654 Ty::Unit
655 | Ty::U8
656 | Ty::U16
657 | Ty::U32
658 | Ty::U64
659 | Ty::I8
660 | Ty::I16
661 | Ty::I32
662 | Ty::I64
663 | Ty::F32
664 | Ty::F64
665 | Ty::Bool
666 | Ty::String => {}
667 }
668}
669
670pub(crate) fn emit_pointer_drop_functions(
671 module: &mut walrus::Module,
672 runtime: &RuntimeFunctions,
673 memory_id: MemoryId,
674 structs: &[ruka_mir::MirStructDecl],
675 enums: &[ruka_mir::MirEnumDecl],
676 program: &ruka_mir::MirProgram,
677) -> Result<BTreeMap<String, FunctionId>, LowerError> {
678 let mut pointer_types = BTreeMap::<String, Ty>::new();
679 let mut visiting_structs = BTreeSet::<String>::new();
680 for (_, function) in program.functions.iter() {
681 for param in function.params.iter().copied() {
682 collect_pointer_types(
683 &function.locals[param].ty,
684 structs,
685 enums,
686 &mut pointer_types,
687 &mut visiting_structs,
688 );
689 }
690 for (_, local) in function.locals.iter() {
691 collect_pointer_types(
692 &local.ty,
693 structs,
694 enums,
695 &mut pointer_types,
696 &mut visiting_structs,
697 );
698 }
699 }
700
701 let mut pointer_drop_functions = BTreeMap::<String, FunctionId>::new();
702 for key in pointer_types.keys() {
703 let builder = FunctionBuilder::new(&mut module.types, &[ValType::I32], &[]);
704 let function_id = builder.finish(vec![module.locals.add(ValType::I32)], &mut module.funcs);
705 let _ = pointer_drop_functions.insert(key.clone(), function_id);
706 }
707
708 let free = runtime_function(runtime, wasm_api::RT_FREE_BYTES_SYMBOL)?;
709 for (key, pointer_ty) in pointer_types {
710 let function_id = *pointer_drop_functions
711 .get(&key)
712 .expect("pointer drop function id should exist");
713 let Ty::Pointer(pointee_ty) = pointer_ty else {
714 continue;
715 };
716 let local_function = module.funcs.get_mut(function_id).kind.unwrap_local_mut();
717 let pointer_local = local_function.args[0];
718 let entry_block = local_function.entry_block();
719 local_function.block_mut(entry_block).instrs.clear();
720 let scratch_ptr_local = module.locals.add(ValType::I32);
721 let scratch_count_local = module.locals.add(ValType::I32);
722 let scratch_size_local = module.locals.add(ValType::I32);
723 let scratch_value_local = module.locals.add(ValType::I32);
724 let scratch_i64_local = module.locals.add(ValType::I64);
725 let mut body = local_function.builder_mut().instr_seq(entry_block);
726 emit_pointer_release_with_free(
727 &mut body,
728 memory_id,
729 pointer_local,
730 &build_release_plan(pointee_ty.as_ref(), structs, enums)?,
731 &pointer_drop_functions,
732 scratch_ptr_local,
733 scratch_count_local,
734 scratch_size_local,
735 scratch_value_local,
736 scratch_i64_local,
737 free.function_id,
738 );
739 }
740
741 Ok(pointer_drop_functions)
742}
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747
748 #[test]
749 fn detects_borrowed_return_types() {
750 assert!(is_borrowed_return_ty(&Ty::RefRo(Box::new(Ty::I64))));
751 assert!(is_borrowed_return_ty(&Ty::RefMut(Box::new(Ty::I64))));
752 }
753
754 #[test]
755 fn accepts_owned_return_types() {
756 assert!(!is_borrowed_return_ty(&Ty::Slice(Box::new(Ty::I64))));
757 assert!(!is_borrowed_return_ty(&Ty::Array {
758 item: Box::new(Ty::I64),
759 len: 4,
760 }));
761 }
762}