1mod expr;
17mod helpers;
18mod stmt;
19
20use super::types::*;
21use crate::mir::analysis::MutabilityError;
22use shape_ast::ast::{self, Span, Statement};
23use std::collections::{HashMap, HashSet};
24
25
26#[derive(Debug, Clone, Copy)]
27pub(super) struct MirLoopContext {
28 pub(super) break_block: BasicBlockId,
29 pub(super) continue_block: BasicBlockId,
30 pub(super) break_value_slot: Option<SlotId>,
31}
32
33#[derive(Debug, Clone)]
34struct TaskBoundaryCaptureScope {
35 outer_locals_cutoff: u16,
36 operands: Vec<Operand>,
37}
38
39#[derive(Debug, Clone)]
40struct MirLocalRecord {
41 name: String,
42 type_info: LocalTypeInfo,
43 binding_info: Option<LoweredBindingInfo>,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct LoweredBindingInfo {
48 pub slot: SlotId,
49 pub name: String,
50 pub declaration_span: Span,
51 pub enforce_immutable_assignment: bool,
52 pub is_explicit_let: bool,
53 pub is_const: bool,
54 pub initialization_point: Option<Point>,
55}
56
57#[derive(Debug, Clone, Copy)]
58pub(super) struct BindingMetadata {
59 declaration_span: Span,
60 enforce_immutable_assignment: bool,
61 is_explicit_let: bool,
62 is_const: bool,
63}
64
65pub struct MirBuilder {
67 name: String,
69 blocks: Vec<BasicBlock>,
71 current_stmts: Vec<MirStatement>,
73 pub(super) current_block: BasicBlockId,
75 current_block_finished: bool,
77 next_block_id: u32,
79 next_local: u16,
81 return_slot: SlotId,
83 next_point: u32,
85 next_loan: u32,
87 locals: Vec<MirLocalRecord>,
89 local_slots: HashMap<String, SlotId>,
91 field_indices: HashMap<String, FieldIdx>,
93 next_field_idx: u16,
95 param_slots: Vec<SlotId>,
97 param_reference_kinds: Vec<Option<BorrowKind>>,
99 scope_bindings: Vec<Vec<(String, Option<SlotId>)>>,
101 loop_contexts: Vec<MirLoopContext>,
103 task_boundary_capture_scopes: Vec<TaskBoundaryCaptureScope>,
105 pub(super) async_scope_depth: u32,
107 exit_block: Option<BasicBlockId>,
109 span: Span,
111 fallback_spans: Vec<Span>,
114 mut_self_container_locals:
121 HashMap<SlotId, crate::compiler::mutation_writeback::ContainerKind>,
122 local_struct_type_names: HashMap<SlotId, String>,
130 local_typed_array_element_types: HashMap<SlotId, shape_value::v2::ConcreteType>,
137 local_declared_scalar_types: HashMap<SlotId, shape_value::v2::ConcreteType>,
143}
144
145#[derive(Debug)]
146pub struct MirLoweringResult {
147 pub mir: MirFunction,
148 pub had_fallbacks: bool,
149 pub fallback_spans: Vec<Span>,
152 pub binding_infos: Vec<LoweredBindingInfo>,
153 pub field_names: HashMap<FieldIdx, String>,
155 pub all_local_names: HashSet<String>,
158}
159
160impl MirBuilder {
165 pub fn new(name: String, span: Span) -> Self {
166 let return_slot = SlotId(0);
167 MirBuilder {
168 name,
169 blocks: Vec::new(),
170 current_stmts: Vec::new(),
171 current_block: BasicBlockId(0),
172 current_block_finished: false,
173 next_block_id: 1,
174 next_local: 1,
175 return_slot,
176 next_point: 0,
177 next_loan: 0,
178 locals: vec![MirLocalRecord {
179 name: "__mir_return".to_string(),
180 type_info: LocalTypeInfo::Unknown,
181 binding_info: None,
182 }],
183 local_slots: HashMap::new(),
184 field_indices: HashMap::new(),
185 next_field_idx: 0,
186 param_slots: Vec::new(),
187 param_reference_kinds: Vec::new(),
188 scope_bindings: vec![Vec::new()],
189 loop_contexts: Vec::new(),
190 task_boundary_capture_scopes: Vec::new(),
191 async_scope_depth: 0,
192 exit_block: None,
193 span,
194 fallback_spans: Vec::new(),
195 mut_self_container_locals: HashMap::new(),
196 local_struct_type_names: HashMap::new(),
197 local_typed_array_element_types: HashMap::new(),
198 local_declared_scalar_types: HashMap::new(),
199 }
200 }
201
202 pub(super) fn record_local_struct_type_name(&mut self, slot: SlotId, type_name: String) {
214 self.local_struct_type_names.insert(slot, type_name);
215 }
216
217 pub(super) fn record_local_typed_array_element_type(
230 &mut self,
231 slot: SlotId,
232 elem: shape_value::v2::ConcreteType,
233 ) {
234 self.local_typed_array_element_types.insert(slot, elem);
235 }
236
237 pub(super) fn record_local_declared_scalar_type(
249 &mut self,
250 slot: SlotId,
251 ct: shape_value::v2::ConcreteType,
252 ) {
253 self.local_declared_scalar_types.insert(slot, ct);
254 }
255
256 pub(super) fn lookup_local_declared_scalar_type(
262 &self,
263 slot: SlotId,
264 ) -> Option<&shape_value::v2::ConcreteType> {
265 self.local_declared_scalar_types.get(&slot)
266 }
267
268 pub(super) fn record_mut_self_container_local(
274 &mut self,
275 slot: SlotId,
276 kind: crate::compiler::mutation_writeback::ContainerKind,
277 ) {
278 self.mut_self_container_locals.insert(slot, kind);
279 }
280
281 pub(super) fn lookup_mut_self_container_local(
285 &self,
286 slot: SlotId,
287 ) -> Option<crate::compiler::mutation_writeback::ContainerKind> {
288 self.mut_self_container_locals.get(&slot).copied()
289 }
290
291 pub fn alloc_local(&mut self, name: String, type_info: LocalTypeInfo) -> SlotId {
293 self.alloc_local_with_binding(name, type_info, None)
294 }
295
296 pub(super) fn alloc_local_binding(
297 &mut self,
298 name: String,
299 type_info: LocalTypeInfo,
300 binding_metadata: BindingMetadata,
301 ) -> SlotId {
302 self.alloc_local_with_binding(name, type_info, Some(binding_metadata))
303 }
304
305 fn alloc_local_with_binding(
306 &mut self,
307 name: String,
308 type_info: LocalTypeInfo,
309 binding_metadata: Option<BindingMetadata>,
310 ) -> SlotId {
311 self.alloc_local_with_binding_options(name, type_info, binding_metadata, true)
312 }
313
314 pub(super) fn alloc_local_with_binding_deferred(
321 &mut self,
322 name: String,
323 type_info: LocalTypeInfo,
324 binding_metadata: Option<BindingMetadata>,
325 ) -> SlotId {
326 self.alloc_local_with_binding_options(name, type_info, binding_metadata, false)
327 }
328
329 fn alloc_local_with_binding_options(
330 &mut self,
331 name: String,
332 type_info: LocalTypeInfo,
333 binding_metadata: Option<BindingMetadata>,
334 bind_now: bool,
335 ) -> SlotId {
336 let slot = SlotId(self.next_local);
337 self.next_local += 1;
338 let binding_info = binding_metadata.map(|binding_metadata| LoweredBindingInfo {
339 slot,
340 name: name.clone(),
341 declaration_span: binding_metadata.declaration_span,
342 enforce_immutable_assignment: binding_metadata.enforce_immutable_assignment,
343 is_explicit_let: binding_metadata.is_explicit_let,
344 is_const: binding_metadata.is_const,
345 initialization_point: None,
346 });
347 self.locals.push(MirLocalRecord {
348 name,
349 type_info,
350 binding_info,
351 });
352 if bind_now {
353 if let Some(local) = self.locals.last()
354 && !local.name.starts_with("__mir_")
355 {
356 self.bind_named_local(local.name.clone(), slot);
357 }
358 }
359 slot
360 }
361
362 pub(super) fn bind_named_local_pub(&mut self, name: String, slot: SlotId) {
366 self.bind_named_local(name, slot);
367 }
368
369 pub fn alloc_temp(&mut self, type_info: LocalTypeInfo) -> SlotId {
371 let name = format!("__mir_tmp{}", self.next_local);
372 self.alloc_local(name, type_info)
373 }
374
375 fn add_param(
377 &mut self,
378 name: String,
379 type_info: LocalTypeInfo,
380 reference_kind: Option<BorrowKind>,
381 binding_metadata: Option<BindingMetadata>,
382 ) -> SlotId {
383 let slot = self.alloc_local_with_binding(name, type_info, binding_metadata);
384 self.param_slots.push(slot);
385 self.param_reference_kinds.push(reference_kind);
386 slot
387 }
388
389 pub fn lookup_local(&self, name: &str) -> Option<SlotId> {
391 self.local_slots.get(name).copied()
392 }
393
394 pub fn visible_named_locals(&self) -> Vec<String> {
395 self.local_slots
396 .keys()
397 .filter(|name| !name.starts_with("__mir_"))
398 .cloned()
399 .collect()
400 }
401
402 pub fn field_idx(&mut self, property: &str) -> FieldIdx {
404 if let Some(idx) = self.field_indices.get(property).copied() {
405 return idx;
406 }
407 let idx = FieldIdx(self.next_field_idx);
408 self.next_field_idx += 1;
409 self.field_indices.insert(property.to_string(), idx);
410 idx
411 }
412
413 pub fn return_slot(&self) -> SlotId {
414 self.return_slot
415 }
416
417 pub fn set_exit_block(&mut self, block: BasicBlockId) {
418 self.exit_block = Some(block);
419 }
420
421 pub fn exit_block(&self) -> BasicBlockId {
422 self.exit_block
423 .expect("MIR builder exit block should be initialized before lowering")
424 }
425
426 pub fn push_scope(&mut self) {
427 self.scope_bindings.push(Vec::new());
428 }
429
430 pub fn pop_scope(&mut self) {
431 if self.scope_bindings.len() <= 1 {
432 return;
433 }
434 if let Some(bindings) = self.scope_bindings.pop() {
435 for (name, previous_slot) in bindings.into_iter().rev() {
436 if let Some(slot) = previous_slot {
437 self.local_slots.insert(name, slot);
438 } else {
439 self.local_slots.remove(&name);
440 }
441 }
442 }
443 }
444
445 fn bind_named_local(&mut self, name: String, slot: SlotId) {
446 if let Some(scope) = self.scope_bindings.last_mut()
447 && !scope.iter().any(|(existing, _)| existing == &name)
448 {
449 scope.push((name.clone(), self.local_slots.get(&name).copied()));
450 }
451 self.local_slots.insert(name, slot);
452 }
453
454 pub fn mark_fallback(&mut self) {
455 self.fallback_spans.push(self.span);
457 }
458
459 pub fn mark_fallback_at(&mut self, span: Span) {
460 self.fallback_spans.push(span);
461 }
462
463 pub fn had_fallbacks(&self) -> bool {
464 !self.fallback_spans.is_empty()
465 }
466
467 pub fn push_loop(
468 &mut self,
469 break_block: BasicBlockId,
470 continue_block: BasicBlockId,
471 break_value_slot: Option<SlotId>,
472 ) {
473 self.loop_contexts.push(MirLoopContext {
474 break_block,
475 continue_block,
476 break_value_slot,
477 });
478 }
479
480 pub fn pop_loop(&mut self) {
481 self.loop_contexts.pop();
482 }
483
484 pub(super) fn current_loop(&self) -> Option<MirLoopContext> {
485 self.loop_contexts.last().copied()
486 }
487
488 pub fn push_task_boundary_capture_scope(&mut self) {
489 self.task_boundary_capture_scopes
490 .push(TaskBoundaryCaptureScope {
491 outer_locals_cutoff: self.next_local,
492 operands: Vec::new(),
493 });
494 }
495
496 pub fn pop_task_boundary_capture_scope(&mut self) -> Vec<Operand> {
497 self.task_boundary_capture_scopes
498 .pop()
499 .map(|scope| scope.operands)
500 .unwrap_or_default()
501 }
502
503 pub fn record_task_boundary_operand(&mut self, operand: Operand) {
504 for scope in &mut self.task_boundary_capture_scopes {
505 if !helpers::operand_crosses_task_boundary(scope.outer_locals_cutoff, &operand) {
506 continue;
507 }
508 if !scope.operands.contains(&operand) {
509 scope.operands.push(operand.clone());
510 }
511 }
512 }
513
514 pub fn record_task_boundary_reference_capture(
515 &mut self,
516 reference_slot: SlotId,
517 borrowed_place: &Place,
518 ) {
519 let reference_operand = Operand::Copy(Place::Local(reference_slot));
520 for scope in &mut self.task_boundary_capture_scopes {
521 if borrowed_place.root_local().0 >= scope.outer_locals_cutoff {
522 continue;
523 }
524 if !scope.operands.contains(&reference_operand) {
525 scope.operands.push(reference_operand.clone());
526 }
527 }
528 }
529
530 pub fn next_point(&mut self) -> Point {
532 let p = Point(self.next_point);
533 self.next_point += 1;
534 p
535 }
536
537 pub fn next_loan(&mut self) -> LoanId {
539 let l = LoanId(self.next_loan);
540 self.next_loan += 1;
541 l
542 }
543
544 pub fn new_block(&mut self) -> BasicBlockId {
546 let id = BasicBlockId(self.next_block_id);
547 self.next_block_id += 1;
548 id
549 }
550
551 pub fn push_stmt(&mut self, kind: StatementKind, span: Span) -> Point {
553 let point = self.next_point();
554 self.current_stmts.push(MirStatement { kind, span, point });
555 point
556 }
557
558 pub fn record_binding_initialization(&mut self, slot: SlotId, point: Point) {
559 if let Some(local) = self.locals.get_mut(slot.0 as usize)
560 && let Some(binding_info) = local.binding_info.as_mut()
561 {
562 binding_info.initialization_point = Some(point);
563 }
564 }
565
566 pub fn finish_block(&mut self, terminator_kind: TerminatorKind, span: Span) {
568 let block = BasicBlock {
569 id: self.current_block,
570 statements: std::mem::take(&mut self.current_stmts),
571 terminator: Terminator {
572 kind: terminator_kind,
573 span,
574 },
575 };
576 self.blocks.push(block);
577 self.current_block_finished = true;
578 }
579
580 pub fn start_block(&mut self, id: BasicBlockId) {
582 self.current_block = id;
583 self.current_stmts.clear();
584 self.current_block_finished = false;
585 }
586
587 pub fn emit_call(
590 &mut self,
591 func: Operand,
592 args: Vec<Operand>,
593 destination: Place,
594 span: Span,
595 ) {
596 let next_bb = self.new_block();
597 self.finish_block(
598 TerminatorKind::Call {
599 func,
600 args,
601 destination,
602 next: next_bb,
603 },
604 span,
605 );
606 self.start_block(next_bb);
607 }
608
609 pub fn build(self) -> MirLoweringResult {
611 let local_types = self
612 .locals
613 .iter()
614 .map(|local| local.type_info.clone())
615 .collect();
616 let binding_infos = self
617 .locals
618 .iter()
619 .filter_map(|local| local.binding_info.clone())
620 .collect();
621 let field_names: HashMap<FieldIdx, String> = self
622 .field_indices
623 .iter()
624 .map(|(name, &idx)| (idx, name.clone()))
625 .collect();
626 let mut blocks = self.blocks;
628 blocks.sort_by_key(|b| b.id.0);
629
630 let had_fallbacks = !self.fallback_spans.is_empty();
631 let fallback_spans = self.fallback_spans;
632 let all_local_names: HashSet<String> = self
633 .locals
634 .iter()
635 .filter(|l| !l.name.starts_with("__mir_"))
636 .map(|l| l.name.clone())
637 .collect();
638
639 MirLoweringResult {
640 mir: MirFunction {
641 name: self.name,
642 blocks,
643 num_locals: self.next_local,
644 param_slots: self.param_slots,
645 param_reference_kinds: self.param_reference_kinds,
646 local_types,
647 span: self.span,
648 field_name_table: field_names.clone(),
649 local_struct_type_names: self.local_struct_type_names,
650 local_typed_array_element_types: self.local_typed_array_element_types,
651 local_declared_scalar_types: self.local_declared_scalar_types,
652 },
653 had_fallbacks,
654 fallback_spans,
655 binding_infos,
656 field_names,
657 all_local_names,
658 }
659 }
660}
661
662pub(super) fn immutable_binding_metadata(
667 declaration_span: Span,
668 is_explicit_let: bool,
669 is_const: bool,
670) -> BindingMetadata {
671 BindingMetadata {
672 declaration_span,
673 enforce_immutable_assignment: true,
674 is_explicit_let,
675 is_const,
676 }
677}
678
679pub fn lower_function_detailed(
681 name: &str,
682 params: &[ast::FunctionParameter],
683 body: &[Statement],
684 span: Span,
685) -> MirLoweringResult {
686 let mut builder = MirBuilder::new(name.to_string(), span);
687
688 for param in params {
690 let type_info = if param.is_reference {
691 LocalTypeInfo::NonCopy } else {
693 LocalTypeInfo::Unknown };
695 let reference_kind = if param.is_mut_reference {
696 Some(BorrowKind::Exclusive)
697 } else if param.is_reference {
698 Some(BorrowKind::Shared)
699 } else {
700 None
701 };
702 let binding_metadata = if param.is_const {
703 Some(immutable_binding_metadata(param.span(), false, true))
704 } else if matches!(reference_kind, Some(BorrowKind::Shared)) {
705 Some(immutable_binding_metadata(param.span(), false, false))
706 } else {
707 None
708 };
709 if let Some(param_name) = param.simple_name() {
710 let slot = builder.add_param(
711 param_name.to_string(),
712 type_info,
713 reference_kind,
714 binding_metadata,
715 );
716 if let Some(annotation) = param.type_annotation.as_ref() {
748 if let Some(shape_value::v2::ConcreteType::Array(elem)) =
749 crate::compiler::v2_map_emission::concrete_type_from_annotation(annotation)
750 {
751 builder.record_local_typed_array_element_type(slot, *elem);
752 }
753 }
754 } else {
755 let slot = builder.add_param(
756 format!("__mir_param{}", builder.param_slots.len()),
757 type_info,
758 reference_kind,
759 None,
760 );
761 stmt::lower_destructure_bindings_from_place(
762 &mut builder,
763 ¶m.pattern,
764 &Place::Local(slot),
765 param.span(),
766 binding_metadata,
767 );
768 }
769 }
770
771 let exit_block = builder.new_block();
773 builder.set_exit_block(exit_block);
774
775 stmt::lower_statements(&mut builder, body, exit_block);
777
778 if !builder.current_block_finished {
780 builder.finish_block(TerminatorKind::Goto(exit_block), span);
781 }
782
783 builder.start_block(exit_block);
785 builder.finish_block(TerminatorKind::Return, span);
786
787 builder.build()
788}
789
790pub fn lower_function(
792 name: &str,
793 params: &[ast::FunctionParameter],
794 body: &[Statement],
795 span: Span,
796) -> MirFunction {
797 lower_function_detailed(name, params, body, span).mir
798}
799
800pub fn compute_mutability_errors(lowering: &MirLoweringResult) -> Vec<MutabilityError> {
801 let tracked_bindings: HashMap<SlotId, &LoweredBindingInfo> = lowering
802 .binding_infos
803 .iter()
804 .filter(|binding| binding.enforce_immutable_assignment)
805 .map(|binding| (binding.slot, binding))
806 .collect();
807 let mut errors = Vec::new();
808
809 for block in &lowering.mir.blocks {
810 for stmt in &block.statements {
811 let StatementKind::Assign(place, _) = &stmt.kind else {
812 continue;
813 };
814 let root = place.root_local();
815 let Some(binding) = tracked_bindings.get(&root) else {
816 continue;
817 };
818 let is_declaration_init = matches!(place, Place::Local(slot) if *slot == root)
819 && binding.initialization_point == Some(stmt.point);
820 if is_declaration_init {
821 continue;
822 }
823 errors.push(MutabilityError {
824 span: stmt.span,
825 variable_name: binding.name.clone(),
826 declaration_span: binding.declaration_span,
827 is_explicit_let: binding.is_explicit_let,
828 is_const: binding.is_const,
829 });
830 }
831 }
832
833 errors
834}
835
836#[cfg(test)]
841mod tests {
842 use super::*;
843 use crate::mir::analysis::BorrowErrorKind;
844 use crate::mir::cfg::ControlFlowGraph;
845 use crate::mir::liveness;
846 use crate::mir::solver;
847 use shape_ast::ast::{self, DestructurePattern, Expr, OwnershipModifier, VarKind};
848
849 fn span() -> Span {
850 Span { start: 0, end: 1 }
851 }
852
853 fn lower_parsed_function(code: &str) -> MirLoweringResult {
854 let program = shape_ast::parser::parse_program(code).expect("parse failed");
855 let func = match &program.items[0] {
856 ast::Item::Function(func, _) => func,
857 _ => panic!("expected function item"),
858 };
859 lower_function_detailed(&func.name, &func.params, &func.body, func.name_span)
860 }
861
862 #[test]
863 fn test_lower_empty_function() {
864 let mir = lower_function("empty", &[], &[], span());
865 assert_eq!(mir.name, "empty");
866 assert!(mir.blocks.len() >= 2); assert_eq!(mir.num_locals, 1);
868 }
869
870 #[test]
871 fn test_lower_simple_var_decl() {
872 let body = vec![Statement::VariableDecl(
873 ast::VariableDecl {
874 kind: VarKind::Let,
875 is_mut: false,
876 pattern: DestructurePattern::Identifier("x".to_string(), span()),
877 type_annotation: None,
878 value: Some(Expr::Literal(ast::Literal::Int(42), span())),
879 ownership: OwnershipModifier::Inferred,
880 },
881 span(),
882 )];
883 let mir = lower_function("test", &[], &body, span());
884 assert!(mir.num_locals >= 1); assert!(mir.blocks.len() >= 2);
887 }
888
889 #[test]
890 fn test_compute_mutability_errors_ignores_binding_initializer() {
891 let lowering = lower_parsed_function(
892 r#"
893 function keep() {
894 let x = 1
895 x
896 }
897 "#,
898 );
899 let errors = compute_mutability_errors(&lowering);
900 assert!(
901 errors.is_empty(),
902 "declaration initializer should not be reported as a mutability error: {:?}",
903 errors
904 );
905 }
906
907 #[test]
908 fn test_compute_mutability_errors_flags_immutable_let_reassignment() {
909 let lowering = lower_parsed_function(
910 r#"
911 function mutate() {
912 let x = 1
913 x = 2
914 x
915 }
916 "#,
917 );
918 let errors = compute_mutability_errors(&lowering);
919 assert_eq!(
920 errors.len(),
921 1,
922 "expected one mutability error, got {errors:?}"
923 );
924 assert_eq!(errors[0].variable_name, "x");
925 assert!(errors[0].is_explicit_let);
926 }
927
928 #[test]
929 fn test_compute_mutability_errors_flags_const_reassignment() {
930 let lowering = lower_parsed_function(
931 r#"
932 function mutate() {
933 const x = 1
934 x = 2
935 x
936 }
937 "#,
938 );
939 let errors = compute_mutability_errors(&lowering);
940 assert_eq!(
941 errors.len(),
942 1,
943 "expected one mutability error, got {errors:?}"
944 );
945 assert_eq!(errors[0].variable_name, "x");
946 assert!(errors[0].is_const);
947 }
948
949 #[test]
950 fn test_compute_mutability_errors_flags_shared_ref_param_write() {
951 let lowering = lower_parsed_function(
952 r#"
953 function mutate(&x) {
954 x = 2
955 x
956 }
957 "#,
958 );
959 let errors = compute_mutability_errors(&lowering);
960 assert_eq!(
961 errors.len(),
962 1,
963 "expected one mutability error, got {errors:?}"
964 );
965 assert_eq!(errors[0].variable_name, "x");
966 assert!(!errors[0].is_explicit_let);
967 }
968
969 #[test]
970 fn test_compute_mutability_errors_flags_const_param_write() {
971 let lowering = lower_parsed_function(
972 r#"
973 function mutate(const x) {
974 x = 2
975 x
976 }
977 "#,
978 );
979 let errors = compute_mutability_errors(&lowering);
980 assert_eq!(
981 errors.len(),
982 1,
983 "expected one mutability error, got {errors:?}"
984 );
985 assert_eq!(errors[0].variable_name, "x");
986 assert!(errors[0].is_const);
987 }
988
989 #[test]
990 fn test_lower_with_liveness() {
991 let body = vec![
993 Statement::VariableDecl(
994 ast::VariableDecl {
995 kind: VarKind::Let,
996 is_mut: false,
997 pattern: DestructurePattern::Identifier("x".to_string(), span()),
998 type_annotation: None,
999 value: Some(Expr::Literal(
1000 ast::Literal::String("hi".to_string()),
1001 span(),
1002 )),
1003 ownership: OwnershipModifier::Inferred,
1004 },
1005 span(),
1006 ),
1007 Statement::VariableDecl(
1008 ast::VariableDecl {
1009 kind: VarKind::Let,
1010 is_mut: false,
1011 pattern: DestructurePattern::Identifier("y".to_string(), span()),
1012 type_annotation: None,
1013 value: Some(Expr::Identifier("x".to_string(), span())),
1014 ownership: OwnershipModifier::Inferred,
1015 },
1016 span(),
1017 ),
1018 Statement::VariableDecl(
1019 ast::VariableDecl {
1020 kind: VarKind::Let,
1021 is_mut: false,
1022 pattern: DestructurePattern::Identifier("kept".to_string(), span()),
1023 type_annotation: None,
1024 value: Some(Expr::Identifier("shared".to_string(), span())),
1025 ownership: OwnershipModifier::Inferred,
1026 },
1027 span(),
1028 ),
1029 ];
1030 let mir = lower_function("test", &[], &body, span());
1031 let cfg = ControlFlowGraph::build(&mir);
1032 let _liveness = liveness::compute_liveness(&mir, &cfg);
1033 }
1035
1036 #[test]
1037 fn test_lower_reference_to_identifier_borrows_original_local() {
1038 let body = vec![
1039 Statement::VariableDecl(
1040 ast::VariableDecl {
1041 kind: VarKind::Let,
1042 is_mut: false,
1043 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1044 type_annotation: None,
1045 value: Some(Expr::Literal(
1046 ast::Literal::String("hi".to_string()),
1047 span(),
1048 )),
1049 ownership: OwnershipModifier::Inferred,
1050 },
1051 span(),
1052 ),
1053 Statement::VariableDecl(
1054 ast::VariableDecl {
1055 kind: VarKind::Let,
1056 is_mut: false,
1057 pattern: DestructurePattern::Identifier("r".to_string(), span()),
1058 type_annotation: None,
1059 value: Some(Expr::Reference {
1060 expr: Box::new(Expr::Identifier("x".to_string(), span())),
1061 is_mutable: false,
1062 span: span(),
1063 }),
1064 ownership: OwnershipModifier::Inferred,
1065 },
1066 span(),
1067 ),
1068 ];
1069 let mir = lower_function("test", &[], &body, span());
1070 let borrow_place = mir
1071 .blocks
1072 .iter()
1073 .flat_map(|block| block.statements.iter())
1074 .find_map(|stmt| match &stmt.kind {
1075 StatementKind::Assign(_, Rvalue::Borrow(_, place)) => Some(place.clone()),
1076 _ => None,
1077 })
1078 .expect("expected borrow statement");
1079 assert_eq!(borrow_place, Place::Local(SlotId(1)));
1080 }
1081
1082 #[test]
1083 fn test_lowered_local_borrow_conflict_is_visible_to_solver() {
1084 let body = vec![
1085 Statement::VariableDecl(
1086 ast::VariableDecl {
1087 kind: VarKind::Let,
1088 is_mut: true,
1089 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1090 type_annotation: None,
1091 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
1092 ownership: OwnershipModifier::Inferred,
1093 },
1094 span(),
1095 ),
1096 Statement::VariableDecl(
1097 ast::VariableDecl {
1098 kind: VarKind::Let,
1099 is_mut: false,
1100 pattern: DestructurePattern::Identifier("shared".to_string(), span()),
1101 type_annotation: None,
1102 value: Some(Expr::Reference {
1103 expr: Box::new(Expr::Identifier("x".to_string(), span())),
1104 is_mutable: false,
1105 span: span(),
1106 }),
1107 ownership: OwnershipModifier::Inferred,
1108 },
1109 span(),
1110 ),
1111 Statement::VariableDecl(
1112 ast::VariableDecl {
1113 kind: VarKind::Let,
1114 is_mut: false,
1115 pattern: DestructurePattern::Identifier("exclusive".to_string(), span()),
1116 type_annotation: None,
1117 value: Some(Expr::Reference {
1118 expr: Box::new(Expr::Identifier("x".to_string(), span())),
1119 is_mutable: true,
1120 span: span(),
1121 }),
1122 ownership: OwnershipModifier::Inferred,
1123 },
1124 span(),
1125 ),
1126 Statement::Return(Some(Expr::Identifier("shared".to_string(), span())), span()),
1127 ];
1128 let mir = lower_function("test", &[], &body, span());
1129 let analysis = solver::analyze(&mir, &Default::default());
1130 assert!(
1131 analysis
1132 .errors
1133 .iter()
1134 .any(|error| error.kind == BorrowErrorKind::ConflictSharedExclusive),
1135 "expected shared/exclusive conflict, got {:?}",
1136 analysis.errors
1137 );
1138 }
1139
1140 #[test]
1141 fn test_lowered_property_borrows_preserve_disjoint_places() {
1142 let body = vec![
1143 Statement::VariableDecl(
1144 ast::VariableDecl {
1145 kind: VarKind::Let,
1146 is_mut: true,
1147 pattern: DestructurePattern::Identifier("pair".to_string(), span()),
1148 type_annotation: None,
1149 value: Some(Expr::Literal(ast::Literal::Int(0), span())),
1150 ownership: OwnershipModifier::Inferred,
1151 },
1152 span(),
1153 ),
1154 Statement::VariableDecl(
1155 ast::VariableDecl {
1156 kind: VarKind::Let,
1157 is_mut: false,
1158 pattern: DestructurePattern::Identifier("left".to_string(), span()),
1159 type_annotation: None,
1160 value: Some(Expr::Reference {
1161 expr: Box::new(Expr::PropertyAccess {
1162 object: Box::new(Expr::Identifier("pair".to_string(), span())),
1163 property: "left".to_string(),
1164 optional: false,
1165 span: span(),
1166 }),
1167 is_mutable: true,
1168 span: span(),
1169 }),
1170 ownership: OwnershipModifier::Inferred,
1171 },
1172 span(),
1173 ),
1174 Statement::VariableDecl(
1175 ast::VariableDecl {
1176 kind: VarKind::Let,
1177 is_mut: false,
1178 pattern: DestructurePattern::Identifier("right".to_string(), span()),
1179 type_annotation: None,
1180 value: Some(Expr::Reference {
1181 expr: Box::new(Expr::PropertyAccess {
1182 object: Box::new(Expr::Identifier("pair".to_string(), span())),
1183 property: "right".to_string(),
1184 optional: false,
1185 span: span(),
1186 }),
1187 is_mutable: true,
1188 span: span(),
1189 }),
1190 ownership: OwnershipModifier::Inferred,
1191 },
1192 span(),
1193 ),
1194 Statement::VariableDecl(
1195 ast::VariableDecl {
1196 kind: VarKind::Let,
1197 is_mut: false,
1198 pattern: DestructurePattern::Identifier("kept".to_string(), span()),
1199 type_annotation: None,
1200 value: Some(Expr::Identifier("shared".to_string(), span())),
1201 ownership: OwnershipModifier::Inferred,
1202 },
1203 span(),
1204 ),
1205 ];
1206 let mir = lower_function("test", &[], &body, span());
1207 let analysis = solver::analyze(&mir, &Default::default());
1208 assert!(
1209 analysis.errors.is_empty(),
1210 "disjoint field borrows should not conflict, got {:?}",
1211 analysis.errors
1212 );
1213 }
1214
1215 #[test]
1216 fn test_lowered_write_while_borrowed_is_visible_to_solver() {
1217 let body = vec![
1218 Statement::VariableDecl(
1219 ast::VariableDecl {
1220 kind: VarKind::Let,
1221 is_mut: true,
1222 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1223 type_annotation: None,
1224 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
1225 ownership: OwnershipModifier::Inferred,
1226 },
1227 span(),
1228 ),
1229 Statement::VariableDecl(
1230 ast::VariableDecl {
1231 kind: VarKind::Let,
1232 is_mut: false,
1233 pattern: DestructurePattern::Identifier("shared".to_string(), span()),
1234 type_annotation: None,
1235 value: Some(Expr::Reference {
1236 expr: Box::new(Expr::Identifier("x".to_string(), span())),
1237 is_mutable: false,
1238 span: span(),
1239 }),
1240 ownership: OwnershipModifier::Inferred,
1241 },
1242 span(),
1243 ),
1244 Statement::Assignment(
1245 ast::Assignment {
1246 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1247 value: Expr::Literal(ast::Literal::Int(2), span()),
1248 },
1249 span(),
1250 ),
1251 Statement::Expression(Expr::Identifier("shared".to_string(), span()), span()),
1252 ];
1253 let mir = lower_function("test", &[], &body, span());
1254 let analysis = solver::analyze(&mir, &Default::default());
1255 assert!(
1256 analysis
1257 .errors
1258 .iter()
1259 .any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed),
1260 "expected write-while-borrowed error, got {:?}",
1261 analysis.errors
1262 );
1263 }
1264
1265 #[test]
1266 fn test_lowered_read_while_exclusive_borrow_is_visible_to_solver() {
1267 let body = vec![
1268 Statement::VariableDecl(
1269 ast::VariableDecl {
1270 kind: VarKind::Let,
1271 is_mut: true,
1272 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1273 type_annotation: None,
1274 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
1275 ownership: OwnershipModifier::Inferred,
1276 },
1277 span(),
1278 ),
1279 Statement::VariableDecl(
1280 ast::VariableDecl {
1281 kind: VarKind::Let,
1282 is_mut: false,
1283 pattern: DestructurePattern::Identifier("exclusive".to_string(), span()),
1284 type_annotation: None,
1285 value: Some(Expr::Reference {
1286 expr: Box::new(Expr::Identifier("x".to_string(), span())),
1287 is_mutable: true,
1288 span: span(),
1289 }),
1290 ownership: OwnershipModifier::Inferred,
1291 },
1292 span(),
1293 ),
1294 Statement::VariableDecl(
1295 ast::VariableDecl {
1296 kind: VarKind::Let,
1297 is_mut: false,
1298 pattern: DestructurePattern::Identifier("copy".to_string(), span()),
1299 type_annotation: None,
1300 value: Some(Expr::Identifier("x".to_string(), span())),
1301 ownership: OwnershipModifier::Inferred,
1302 },
1303 span(),
1304 ),
1305 Statement::Expression(Expr::Identifier("exclusive".to_string(), span()), span()),
1306 ];
1307 let mir = lower_function("test", &[], &body, span());
1308 let analysis = solver::analyze(&mir, &Default::default());
1309 assert!(
1310 analysis
1311 .errors
1312 .iter()
1313 .any(|error| error.kind == BorrowErrorKind::ReadWhileExclusivelyBorrowed),
1314 "expected read-while-exclusive error, got {:?}",
1315 analysis.errors
1316 );
1317 }
1318
1319 #[test]
1320 fn test_lowered_returned_ref_alias_is_visible_to_solver() {
1321 let body = vec![
1322 Statement::VariableDecl(
1323 ast::VariableDecl {
1324 kind: VarKind::Let,
1325 is_mut: false,
1326 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1327 type_annotation: None,
1328 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
1329 ownership: OwnershipModifier::Inferred,
1330 },
1331 span(),
1332 ),
1333 Statement::VariableDecl(
1334 ast::VariableDecl {
1335 kind: VarKind::Let,
1336 is_mut: false,
1337 pattern: DestructurePattern::Identifier("r".to_string(), span()),
1338 type_annotation: None,
1339 value: Some(Expr::Reference {
1340 expr: Box::new(Expr::Identifier("x".to_string(), span())),
1341 is_mutable: false,
1342 span: span(),
1343 }),
1344 ownership: OwnershipModifier::Inferred,
1345 },
1346 span(),
1347 ),
1348 Statement::VariableDecl(
1349 ast::VariableDecl {
1350 kind: VarKind::Let,
1351 is_mut: false,
1352 pattern: DestructurePattern::Identifier("alias".to_string(), span()),
1353 type_annotation: None,
1354 value: Some(Expr::Identifier("r".to_string(), span())),
1355 ownership: OwnershipModifier::Inferred,
1356 },
1357 span(),
1358 ),
1359 Statement::Return(Some(Expr::Identifier("alias".to_string(), span())), span()),
1360 ];
1361 let mir = lower_function("test", &[], &body, span());
1362 let analysis = solver::analyze(&mir, &Default::default());
1363 assert!(
1364 analysis
1365 .errors
1366 .iter()
1367 .any(|error| error.kind == BorrowErrorKind::ReferenceEscape),
1368 "expected reference-escape error, got {:?}",
1369 analysis.errors
1370 );
1371 }
1372
1373 #[test]
1374 fn test_lowered_array_direct_ref_escape_is_visible_to_solver() {
1375 let lowering = lower_parsed_function(
1376 r#"
1377 function test() {
1378 let x = 1
1379 let arr = [&x]
1380 }
1381 "#,
1382 );
1383 assert!(!lowering.had_fallbacks);
1384 let analysis = solver::analyze(&lowering.mir, &Default::default());
1385 assert!(analysis.errors.is_empty());
1386 }
1387
1388 #[test]
1389 fn test_lowered_array_indirect_ref_escape_is_visible_to_solver() {
1390 let lowering = lower_parsed_function(
1391 r#"
1392 function test() {
1393 let x = 1
1394 let r = &x
1395 let arr = [r]
1396 }
1397 "#,
1398 );
1399 assert!(!lowering.had_fallbacks);
1400 let analysis = solver::analyze(&lowering.mir, &Default::default());
1401 assert!(analysis.errors.is_empty());
1402 }
1403
1404 #[test]
1405 fn test_lowered_object_direct_ref_escape_is_visible_to_solver() {
1406 let lowering = lower_parsed_function(
1407 r#"
1408 function test() {
1409 let x = 1
1410 let obj = { value: &x }
1411 }
1412 "#,
1413 );
1414 assert!(!lowering.had_fallbacks);
1415 let analysis = solver::analyze(&lowering.mir, &Default::default());
1416 assert!(analysis.errors.is_empty());
1417 }
1418
1419 #[test]
1420 fn test_lowered_object_indirect_ref_escape_is_visible_to_solver() {
1421 let lowering = lower_parsed_function(
1422 r#"
1423 function test() {
1424 let x = 1
1425 let r = &x
1426 let obj = { value: r }
1427 }
1428 "#,
1429 );
1430 assert!(!lowering.had_fallbacks);
1431 let analysis = solver::analyze(&lowering.mir, &Default::default());
1432 assert!(analysis.errors.is_empty());
1433 }
1434
1435 #[test]
1436 fn test_lowered_struct_direct_ref_escape_is_visible_to_solver() {
1437 let lowering = lower_parsed_function(
1438 r#"
1439 function test() {
1440 let x = 1
1441 let point = Point { value: &x }
1442 }
1443 "#,
1444 );
1445 assert!(!lowering.had_fallbacks);
1446 let analysis = solver::analyze(&lowering.mir, &Default::default());
1447 assert!(analysis.errors.is_empty());
1448 }
1449
1450 #[test]
1451 fn test_lowered_struct_indirect_ref_escape_is_visible_to_solver() {
1452 let lowering = lower_parsed_function(
1453 r#"
1454 function test() {
1455 let x = 1
1456 let r = &x
1457 let point = Point { value: r }
1458 }
1459 "#,
1460 );
1461 assert!(!lowering.had_fallbacks);
1462 let analysis = solver::analyze(&lowering.mir, &Default::default());
1463 assert!(analysis.errors.is_empty());
1464 }
1465
1466 #[test]
1467 fn test_lowered_enum_tuple_direct_ref_escape_is_visible_to_solver() {
1468 let lowering = lower_parsed_function(
1469 r#"
1470 function test() {
1471 let x = 1
1472 let value = Maybe::Some(&x)
1473 }
1474 "#,
1475 );
1476 assert!(!lowering.had_fallbacks);
1477 let analysis = solver::analyze(&lowering.mir, &Default::default());
1478 assert!(analysis.errors.is_empty());
1479 }
1480
1481 #[test]
1482 fn test_lowered_enum_tuple_indirect_ref_escape_is_visible_to_solver() {
1483 let lowering = lower_parsed_function(
1484 r#"
1485 function test() {
1486 let x = 1
1487 let r = &x
1488 let value = Maybe::Some(r)
1489 }
1490 "#,
1491 );
1492 assert!(!lowering.had_fallbacks);
1493 let analysis = solver::analyze(&lowering.mir, &Default::default());
1494 assert!(analysis.errors.is_empty());
1495 }
1496
1497 #[test]
1498 fn test_lowered_enum_struct_direct_ref_escape_is_visible_to_solver() {
1499 let lowering = lower_parsed_function(
1500 r#"
1501 function test() {
1502 let x = 1
1503 let value = Maybe::Err { code: &x }
1504 }
1505 "#,
1506 );
1507 assert!(!lowering.had_fallbacks);
1508 let analysis = solver::analyze(&lowering.mir, &Default::default());
1509 assert!(analysis.errors.is_empty());
1510 }
1511
1512 #[test]
1513 fn test_lowered_enum_struct_indirect_ref_escape_is_visible_to_solver() {
1514 let lowering = lower_parsed_function(
1515 r#"
1516 function test() {
1517 let x = 1
1518 let r = &x
1519 let value = Maybe::Err { code: r }
1520 }
1521 "#,
1522 );
1523 assert!(!lowering.had_fallbacks);
1524 let analysis = solver::analyze(&lowering.mir, &Default::default());
1525 assert!(analysis.errors.is_empty());
1526 }
1527
1528 #[test]
1529 fn test_lowered_use_after_explicit_move_is_visible_to_solver() {
1530 let body = vec![
1531 Statement::VariableDecl(
1532 ast::VariableDecl {
1533 kind: VarKind::Let,
1534 is_mut: false,
1535 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1536 type_annotation: None,
1537 value: Some(Expr::Literal(
1538 ast::Literal::String("hi".to_string()),
1539 span(),
1540 )),
1541 ownership: OwnershipModifier::Inferred,
1542 },
1543 span(),
1544 ),
1545 Statement::VariableDecl(
1546 ast::VariableDecl {
1547 kind: VarKind::Let,
1548 is_mut: false,
1549 pattern: DestructurePattern::Identifier("y".to_string(), span()),
1550 type_annotation: None,
1551 value: Some(Expr::Identifier("x".to_string(), span())),
1552 ownership: OwnershipModifier::Move,
1553 },
1554 span(),
1555 ),
1556 Statement::VariableDecl(
1557 ast::VariableDecl {
1558 kind: VarKind::Let,
1559 is_mut: false,
1560 pattern: DestructurePattern::Identifier("z".to_string(), span()),
1561 type_annotation: None,
1562 value: Some(Expr::Identifier("x".to_string(), span())),
1563 ownership: OwnershipModifier::Inferred,
1564 },
1565 span(),
1566 ),
1567 ];
1568 let mir = lower_function("test", &[], &body, span());
1569 let analysis = solver::analyze(&mir, &Default::default());
1570 assert!(
1571 analysis
1572 .errors
1573 .iter()
1574 .any(|error| error.kind == BorrowErrorKind::UseAfterMove),
1575 "expected use-after-move error, got {:?}",
1576 analysis.errors
1577 );
1578 }
1579
1580 #[test]
1581 fn test_lowered_while_expr_write_while_borrowed_is_visible_to_solver() {
1582 let lowering = lower_parsed_function(
1583 r#"
1584 function test() {
1585 let mut x = 1
1586 let y = while true {
1587 let shared = &x
1588 x = 2
1589 shared
1590 0
1591 }
1592 }
1593 "#,
1594 );
1595 assert!(!lowering.had_fallbacks);
1596 let analysis = solver::analyze(&lowering.mir, &Default::default());
1597 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1598 }
1599
1600 #[test]
1601 fn test_lowered_for_expr_write_while_borrowed_is_visible_to_solver() {
1602 let lowering = lower_parsed_function(
1603 r#"
1604 function test(items) {
1605 let mut x = 1
1606 let y = for item in items {
1607 let shared = &x
1608 x = 2
1609 shared
1610 0
1611 }
1612 }
1613 "#,
1614 );
1615 assert!(!lowering.had_fallbacks);
1616 let analysis = solver::analyze(&lowering.mir, &Default::default());
1617 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1618 }
1619
1620 #[test]
1621 fn test_lowered_loop_expr_break_value_write_while_borrowed_is_visible_to_solver() {
1622 let lowering = lower_parsed_function(
1623 r#"
1624 function test() {
1625 let mut x = 1
1626 let y = loop {
1627 let shared = &x
1628 x = 2
1629 shared
1630 break 0
1631 }
1632 }
1633 "#,
1634 );
1635 assert!(!lowering.had_fallbacks);
1636 let analysis = solver::analyze(&lowering.mir, &Default::default());
1637 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1638 }
1639
1640 #[test]
1641 fn test_lowered_continue_expression_in_while_body_stays_supported() {
1642 let lowering = lower_parsed_function(
1643 r#"
1644 function test(flag) {
1645 let mut x = 1
1646 let y = while flag {
1647 if flag { continue } else { x }
1648 }
1649 }
1650 "#,
1651 );
1652 assert!(!lowering.had_fallbacks);
1653 let analysis = solver::analyze(&lowering.mir, &Default::default());
1654 assert!(analysis.errors.is_empty());
1655 }
1656
1657 #[test]
1658 fn test_lowered_match_expression_write_while_borrowed_is_visible_to_solver() {
1659 let lowering = lower_parsed_function(
1660 r#"
1661 function test(flag) {
1662 let mut x = 1
1663 let y = match flag {
1664 true => {
1665 let shared = &x
1666 x = 2
1667 shared
1668 0
1669 }
1670 _ => 0
1671 }
1672 }
1673 "#,
1674 );
1675 assert!(!lowering.had_fallbacks);
1676 let analysis = solver::analyze(&lowering.mir, &Default::default());
1677 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1678 }
1679
1680 #[test]
1681 fn test_lowered_match_expression_identifier_guard_stays_supported() {
1682 let lowering = lower_parsed_function(
1683 r#"
1684 function test(v) {
1685 let y = match v {
1686 x where x > 0 => x
1687 _ => 0
1688 }
1689 }
1690 "#,
1691 );
1692 assert!(!lowering.had_fallbacks);
1693 let analysis = solver::analyze(&lowering.mir, &Default::default());
1694 assert!(analysis.errors.is_empty());
1695 }
1696
1697 #[test]
1698 fn test_lowered_match_expression_array_pattern_write_while_borrowed_is_visible_to_solver() {
1699 let lowering = lower_parsed_function(
1700 r#"
1701 function test(pair) {
1702 let mut x = 1
1703 let y = match pair {
1704 [left, right] => {
1705 let shared = &x
1706 x = 2
1707 shared
1708 0
1709 }
1710 _ => 0
1711 }
1712 }
1713 "#,
1714 );
1715 assert!(!lowering.had_fallbacks);
1716 let analysis = solver::analyze(&lowering.mir, &Default::default());
1717 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1718 }
1719
1720 #[test]
1721 fn test_lowered_match_expression_object_pattern_write_while_borrowed_is_visible_to_solver() {
1722 let lowering = lower_parsed_function(
1723 r#"
1724 function test(obj) {
1725 let mut x = 1
1726 let y = match obj {
1727 { left: l, right: r } => {
1728 let shared = &x
1729 x = 2
1730 shared
1731 0
1732 }
1733 _ => 0
1734 }
1735 }
1736 "#,
1737 );
1738 assert!(!lowering.had_fallbacks);
1739 let analysis = solver::analyze(&lowering.mir, &Default::default());
1740 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1741 }
1742
1743 #[test]
1744 fn test_lowered_match_expression_constructor_pattern_write_while_borrowed_is_visible_to_solver()
1745 {
1746 let lowering = lower_parsed_function(
1747 r#"
1748 function test(opt) {
1749 let mut x = 1
1750 let y = match opt {
1751 Some(v) => {
1752 let shared = &x
1753 x = 2
1754 shared
1755 0
1756 }
1757 None => 0
1758 }
1759 }
1760 "#,
1761 );
1762 assert!(!lowering.had_fallbacks);
1763 let analysis = solver::analyze(&lowering.mir, &Default::default());
1764 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1765 }
1766
1767 #[test]
1781 fn test_lowered_typed_pattern_match_emits_type_pattern_test() {
1782 use crate::mir::types::{Rvalue, StatementKind};
1783 let lowering = lower_parsed_function(
1784 r#"
1785 function describe(x) {
1786 match x {
1787 n: int => 100
1788 s: string => 200
1789 }
1790 }
1791 "#,
1792 );
1793 assert!(!lowering.had_fallbacks);
1794 let typed_tests: Vec<&Rvalue> = lowering
1795 .mir
1796 .blocks
1797 .iter()
1798 .flat_map(|b| b.statements.iter())
1799 .filter_map(|stmt| match &stmt.kind {
1800 StatementKind::Assign(_, rv @ Rvalue::TypePatternTest { .. }) => Some(rv),
1801 _ => None,
1802 })
1803 .collect();
1804 assert_eq!(
1805 typed_tests.len(),
1806 2,
1807 "expected one TypePatternTest per typed arm (int + string); got {} -- MIR: {:#?}",
1808 typed_tests.len(),
1809 lowering.mir.blocks,
1810 );
1811 }
1812
1813 #[test]
1828 fn test_lowered_user_enum_constructor_pattern_match_emits_enum_discriminant_test() {
1829 use crate::mir::types::{Rvalue, StatementKind};
1830 let lowering = lower_parsed_function(
1831 r#"
1832 function show(c) {
1833 match c {
1834 Color::Red => 1
1835 Color::Green => 2
1836 Color::Blue => 3
1837 }
1838 }
1839 "#,
1840 );
1841 assert!(!lowering.had_fallbacks);
1842 let discriminant_tests: Vec<&Rvalue> = lowering
1843 .mir
1844 .blocks
1845 .iter()
1846 .flat_map(|b| b.statements.iter())
1847 .filter_map(|stmt| match &stmt.kind {
1848 StatementKind::Assign(_, rv @ Rvalue::EnumDiscriminantTest { .. }) => Some(rv),
1849 _ => None,
1850 })
1851 .collect();
1852 assert_eq!(
1853 discriminant_tests.len(),
1854 3,
1855 "expected one EnumDiscriminantTest per Color::* arm (Red + Green + Blue); \
1856 got {} -- MIR: {:#?}",
1857 discriminant_tests.len(),
1858 lowering.mir.blocks,
1859 );
1860 let mut variant_names: Vec<&str> = discriminant_tests
1863 .iter()
1864 .filter_map(|rv| match rv {
1865 Rvalue::EnumDiscriminantTest {
1866 enum_name,
1867 variant_name,
1868 ..
1869 } => {
1870 assert_eq!(
1871 enum_name.as_deref(),
1872 Some("Color"),
1873 "enum_name should be stamped verbatim from `Color::...`",
1874 );
1875 Some(variant_name.as_str())
1876 }
1877 _ => None,
1878 })
1879 .collect();
1880 variant_names.sort();
1881 assert_eq!(variant_names, vec!["Blue", "Green", "Red"]);
1882 }
1883
1884 #[test]
1885 fn test_lowered_destructure_var_decl_write_while_borrowed_is_visible_to_solver() {
1886 let lowering = lower_parsed_function(
1887 r#"
1888 function test(pair) {
1889 var [left, right] = pair
1890 let shared = &left
1891 left = 2
1892 shared
1893 }
1894 "#,
1895 );
1896 assert!(!lowering.had_fallbacks);
1897 let analysis = solver::analyze(&lowering.mir, &Default::default());
1898 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1899 }
1900
1901 #[test]
1902 fn test_lowered_destructure_param_write_while_borrowed_is_visible_to_solver() {
1903 let lowering = lower_parsed_function(
1904 r#"
1905 function test([left, right]) {
1906 let mut left_copy = left
1907 let shared = &left_copy
1908 left_copy = 2
1909 shared
1910 }
1911 "#,
1912 );
1913 assert!(!lowering.had_fallbacks);
1914 let analysis = solver::analyze(&lowering.mir, &Default::default());
1915 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1916 }
1917
1918 #[test]
1919 fn test_lowered_destructure_assignment_stays_supported() {
1920 let pair_param = ast::FunctionParameter {
1921 pattern: DestructurePattern::Identifier("pair".to_string(), span()),
1922 is_const: false,
1923 is_reference: false,
1924 is_mut_reference: false,
1925 is_out: false,
1926 type_annotation: None,
1927 default_value: None,
1928 };
1929 let body = vec![
1930 Statement::VariableDecl(
1931 ast::VariableDecl {
1932 kind: VarKind::Let,
1933 is_mut: true,
1934 pattern: DestructurePattern::Identifier("left".to_string(), span()),
1935 type_annotation: None,
1936 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
1937 ownership: OwnershipModifier::Inferred,
1938 },
1939 span(),
1940 ),
1941 Statement::VariableDecl(
1942 ast::VariableDecl {
1943 kind: VarKind::Let,
1944 is_mut: true,
1945 pattern: DestructurePattern::Identifier("right".to_string(), span()),
1946 type_annotation: None,
1947 value: Some(Expr::Literal(ast::Literal::Int(2), span())),
1948 ownership: OwnershipModifier::Inferred,
1949 },
1950 span(),
1951 ),
1952 Statement::Assignment(
1953 ast::Assignment {
1954 pattern: DestructurePattern::Array(vec![
1955 DestructurePattern::Identifier("left".to_string(), span()),
1956 DestructurePattern::Identifier("right".to_string(), span()),
1957 ]),
1958 value: Expr::Identifier("pair".to_string(), span()),
1959 },
1960 span(),
1961 ),
1962 Statement::VariableDecl(
1963 ast::VariableDecl {
1964 kind: VarKind::Let,
1965 is_mut: false,
1966 pattern: DestructurePattern::Identifier("shared".to_string(), span()),
1967 type_annotation: None,
1968 value: Some(Expr::Reference {
1969 expr: Box::new(Expr::Identifier("left".to_string(), span())),
1970 is_mutable: false,
1971 span: span(),
1972 }),
1973 ownership: OwnershipModifier::Inferred,
1974 },
1975 span(),
1976 ),
1977 Statement::Assignment(
1978 ast::Assignment {
1979 pattern: DestructurePattern::Identifier("left".to_string(), span()),
1980 value: Expr::Literal(ast::Literal::Int(3), span()),
1981 },
1982 span(),
1983 ),
1984 Statement::Expression(Expr::Identifier("shared".to_string(), span()), span()),
1985 ];
1986 let lowering = lower_function_detailed("test", &[pair_param], &body, span());
1987 assert!(!lowering.had_fallbacks);
1988 let analysis = solver::analyze(&lowering.mir, &Default::default());
1989 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1990 }
1991
1992 #[test]
1993 fn test_lowered_destructure_rest_pattern_write_while_borrowed_is_visible_to_solver() {
1994 let lowering = lower_parsed_function(
1995 r#"
1996 function test(items) {
1997 var [head, ...tail] = items
1998 let shared = &tail
1999 tail = items
2000 shared
2001 }
2002 "#,
2003 );
2004 assert!(!lowering.had_fallbacks);
2005 let analysis = solver::analyze(&lowering.mir, &Default::default());
2006 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
2007 }
2008
2009 #[test]
2010 fn test_lowered_decomposition_pattern_write_while_borrowed_is_visible_to_solver() {
2011 let lowering = lower_parsed_function(
2012 r#"
2013 function test(merged) {
2014 var (left: {x}, right: {y}) = merged
2015 let shared = &left
2016 left = merged
2017 shared
2018 }
2019 "#,
2020 );
2021 assert!(!lowering.had_fallbacks);
2022 let analysis = solver::analyze(&lowering.mir, &Default::default());
2023 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
2024 }
2025
2026 #[test]
2027 fn test_lowered_supported_runtime_opaque_expressions_stay_supported() {
2028 let mut overrides = std::collections::HashMap::new();
2029 overrides.insert(
2030 "digits".to_string(),
2031 Expr::Literal(ast::Literal::Int(2), span()),
2032 );
2033 let body = vec![
2034 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("x".to_string(), span()), type_annotation: None, value: Some(Expr::Literal(ast::Literal::Int(1), span())), ownership: OwnershipModifier::Inferred }, span()),
2035 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("arr".to_string(), span()), type_annotation: None, value: Some(Expr::Array(vec![Expr::Identifier("x".to_string(), span()), Expr::Literal(ast::Literal::Int(2), span())], span())), ownership: OwnershipModifier::Inferred }, span()),
2036 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("obj".to_string(), span()), type_annotation: None, value: Some(Expr::Object(vec![ast::ObjectEntry::Field { key: "left".to_string(), value: Expr::Identifier("x".to_string(), span()), type_annotation: None }, ast::ObjectEntry::Spread(Expr::Identifier("arr".to_string(), span()))], span())), ownership: OwnershipModifier::Inferred }, span()),
2037 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("unary".to_string(), span()), type_annotation: None, value: Some(Expr::UnaryOp { op: ast::UnaryOp::Neg, operand: Box::new(Expr::Identifier("x".to_string(), span())), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2038 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("fuzzy".to_string(), span()), type_annotation: None, value: Some(Expr::FuzzyComparison { left: Box::new(Expr::Identifier("x".to_string(), span())), op: ast::operators::FuzzyOp::Equal, right: Box::new(Expr::Literal(ast::Literal::Int(1), span())), tolerance: ast::operators::FuzzyTolerance::Percentage(0.02), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2039 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("slice".to_string(), span()), type_annotation: None, value: Some(Expr::IndexAccess { object: Box::new(Expr::Identifier("arr".to_string(), span())), index: Box::new(Expr::Literal(ast::Literal::Int(0), span())), end_index: Some(Box::new(Expr::Literal(ast::Literal::Int(1), span()))), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2040 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("asserted".to_string(), span()), type_annotation: None, value: Some(Expr::TypeAssertion { expr: Box::new(Expr::Identifier("x".to_string(), span())), type_annotation: ast::TypeAnnotation::Basic("int".to_string()), meta_param_overrides: Some(overrides), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2041 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("instance".to_string(), span()), type_annotation: None, value: Some(Expr::InstanceOf { expr: Box::new(Expr::Identifier("x".to_string(), span())), type_annotation: ast::TypeAnnotation::Basic("int".to_string()), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2042 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("variant".to_string(), span()), type_annotation: None, value: Some(Expr::EnumConstructor { enum_name: "Option".into(), variant: "Some".to_string(), payload: ast::EnumConstructorPayload::Tuple(vec![Expr::Identifier("x".to_string(), span())]), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2043 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("call".to_string(), span()), type_annotation: None, value: Some(Expr::MethodCall { receiver: Box::new(Expr::Identifier("obj".to_string(), span())), method: "touch".to_string(), args: vec![Expr::Identifier("x".to_string(), span())], named_args: vec![("tail".to_string(), Expr::IndexAccess { object: Box::new(Expr::Identifier("arr".to_string(), span())), index: Box::new(Expr::Literal(ast::Literal::Int(0), span())), end_index: Some(Box::new(Expr::Literal(ast::Literal::Int(1), span()))), span: span() })], optional: false, span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2044 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("range".to_string(), span()), type_annotation: None, value: Some(Expr::Range { start: Some(Box::new(Expr::Literal(ast::Literal::Int(0), span()))), end: Some(Box::new(Expr::Identifier("x".to_string(), span()))), kind: ast::RangeKind::Exclusive, span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2045 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("contextual".to_string(), span()), type_annotation: None, value: Some(Expr::TimeframeContext { timeframe: ast::Timeframe::new(5, ast::TimeframeUnit::Minute), expr: Box::new(Expr::Identifier("x".to_string(), span())), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2046 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("using_impl".to_string(), span()), type_annotation: None, value: Some(Expr::UsingImpl { expr: Box::new(Expr::Identifier("x".to_string(), span())), impl_name: "Tracked".to_string(), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2047 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("simulation".to_string(), span()), type_annotation: None, value: Some(Expr::SimulationCall { name: "sim".to_string(), params: vec![("value".to_string(), Expr::Identifier("x".to_string(), span()))], span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2048 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("struct_lit".to_string(), span()), type_annotation: None, value: Some(Expr::StructLiteral { type_name: "Point".into(), fields: vec![("x".to_string(), Expr::Identifier("x".to_string(), span()))], span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2049 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("annotated".to_string(), span()), type_annotation: None, value: Some(Expr::Annotated { annotation: ast::Annotation { name: "trace".to_string(), args: vec![Expr::Identifier("x".to_string(), span())], span: span() }, target: Box::new(Expr::Identifier("x".to_string(), span())), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2050 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("rows".to_string(), span()), type_annotation: None, value: Some(Expr::TableRows(vec![vec![Expr::Identifier("x".to_string(), span()), Expr::Literal(ast::Literal::Int(2), span())], vec![Expr::Literal(ast::Literal::Int(3), span()), Expr::Literal(ast::Literal::Int(4), span())]], span())), ownership: OwnershipModifier::Inferred }, span()),
2051 ];
2052 let lowering = lower_function_detailed("test", &[], &body, span());
2053 assert!(!lowering.had_fallbacks);
2054 }
2055
2056 #[test]
2057 fn test_lowered_assignment_expr_write_while_borrowed_is_visible_to_solver() {
2058 let body = vec![
2059 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: true, pattern: DestructurePattern::Identifier("x".to_string(), span()), type_annotation: None, value: Some(Expr::Literal(ast::Literal::Int(1), span())), ownership: OwnershipModifier::Inferred }, span()),
2060 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("shared".to_string(), span()), type_annotation: None, value: Some(Expr::Reference { expr: Box::new(Expr::Identifier("x".to_string(), span())), is_mutable: false, span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2061 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("y".to_string(), span()), type_annotation: None, value: Some(Expr::Assign(Box::new(ast::AssignExpr { target: Box::new(Expr::Identifier("x".to_string(), span())), value: Box::new(Expr::Literal(ast::Literal::Int(2), span())) }), span())), ownership: OwnershipModifier::Inferred }, span()),
2062 Statement::Return(Some(Expr::Identifier("shared".to_string(), span())), span()),
2063 ];
2064 let lowering = lower_function_detailed("test", &[], &body, span());
2065 assert!(!lowering.had_fallbacks);
2066 let analysis = solver::analyze(&lowering.mir, &Default::default());
2067 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
2068 }
2069
2070 #[test]
2071 fn test_lowered_property_assignment_expr_preserves_disjoint_places() {
2072 let body = vec![
2073 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: true, pattern: DestructurePattern::Identifier("pair".to_string(), span()), type_annotation: None, value: Some(Expr::Literal(ast::Literal::String("pair".to_string()), span())), ownership: OwnershipModifier::Inferred }, span()),
2074 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("left".to_string(), span()), type_annotation: None, value: Some(Expr::Reference { expr: Box::new(Expr::PropertyAccess { object: Box::new(Expr::Identifier("pair".to_string(), span())), property: "left".to_string(), optional: false, span: span() }), is_mutable: false, span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2075 Statement::Expression(Expr::Assign(Box::new(ast::AssignExpr { target: Box::new(Expr::PropertyAccess { object: Box::new(Expr::Identifier("pair".to_string(), span())), property: "right".to_string(), optional: false, span: span() }), value: Box::new(Expr::Literal(ast::Literal::String("updated".to_string()), span())) }), span()), span()),
2076 ];
2077 let lowering = lower_function_detailed("test", &[], &body, span());
2078 assert!(!lowering.had_fallbacks);
2079 let analysis = solver::analyze(&lowering.mir, &Default::default());
2080 assert!(analysis.errors.is_empty());
2081 }
2082
2083 #[test]
2084 fn test_lowered_property_assignment_direct_ref_escape_is_visible_to_solver() {
2085 let lowering = lower_parsed_function(r#"
2086 function test() {
2087 var obj = { value: 0 }
2088 let x = 1
2089 obj.value = &x
2090 0
2091 }
2092 "#);
2093 assert!(!lowering.had_fallbacks);
2094 let analysis = solver::analyze(&lowering.mir, &Default::default());
2095 assert!(analysis.errors.is_empty());
2096 }
2097
2098 #[test]
2099 fn test_lowered_property_assignment_indirect_ref_escape_is_visible_to_solver() {
2100 let lowering = lower_parsed_function(r#"
2101 function test() {
2102 var obj = { value: 0 }
2103 let x = 1
2104 let r = &x
2105 obj.value = r
2106 0
2107 }
2108 "#);
2109 assert!(!lowering.had_fallbacks);
2110 let analysis = solver::analyze(&lowering.mir, &Default::default());
2111 assert!(analysis.errors.is_empty());
2112 }
2113
2114 #[test]
2115 fn test_lowered_index_assignment_direct_ref_escape_is_visible_to_solver() {
2116 let lowering = lower_parsed_function(r#"
2117 function test() {
2118 var arr = [0]
2119 let x = 1
2120 arr[0] = &x
2121 0
2122 }
2123 "#);
2124 assert!(!lowering.had_fallbacks);
2125 let analysis = solver::analyze(&lowering.mir, &Default::default());
2126 assert!(analysis.errors.is_empty());
2127 }
2128
2129 #[test]
2130 fn test_lowered_index_assignment_indirect_ref_escape_is_visible_to_solver() {
2131 let lowering = lower_parsed_function(r#"
2132 function test() {
2133 var arr = [0]
2134 let x = 1
2135 let r = &x
2136 arr[0] = r
2137 0
2138 }
2139 "#);
2140 assert!(!lowering.had_fallbacks);
2141 let analysis = solver::analyze(&lowering.mir, &Default::default());
2142 assert!(analysis.errors.is_empty());
2143 }
2144
2145 #[test]
2146 fn test_lowered_block_expr_write_while_borrowed_is_visible_to_solver() {
2147 let body = vec![
2148 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: true, pattern: DestructurePattern::Identifier("x".to_string(), span()), type_annotation: None, value: Some(Expr::Literal(ast::Literal::Int(1), span())), ownership: OwnershipModifier::Inferred }, span()),
2149 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("shared".to_string(), span()), type_annotation: None, value: Some(Expr::Block(ast::BlockExpr { items: vec![ast::BlockItem::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("inner".to_string(), span()), type_annotation: None, value: Some(Expr::Reference { expr: Box::new(Expr::Identifier("x".to_string(), span())), is_mutable: false, span: span() }), ownership: OwnershipModifier::Inferred }), ast::BlockItem::Expression(Expr::Identifier("inner".to_string(), span()))] }, span())), ownership: OwnershipModifier::Inferred }, span()),
2150 Statement::Assignment(ast::Assignment { pattern: DestructurePattern::Identifier("x".to_string(), span()), value: Expr::Literal(ast::Literal::Int(2), span()) }, span()),
2151 Statement::Expression(Expr::Identifier("shared".to_string(), span()), span()),
2152 ];
2153 let lowering = lower_function_detailed("test", &[], &body, span());
2154 assert!(!lowering.had_fallbacks);
2155 let analysis = solver::analyze(&lowering.mir, &Default::default());
2156 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
2157 }
2158
2159 #[test]
2160 fn test_lowered_let_expr_write_while_borrowed_is_visible_to_solver() {
2161 let body = vec![
2162 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: true, pattern: DestructurePattern::Identifier("x".to_string(), span()), type_annotation: None, value: Some(Expr::Literal(ast::Literal::Int(1), span())), ownership: OwnershipModifier::Inferred }, span()),
2163 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("shared".to_string(), span()), type_annotation: None, value: Some(Expr::Let(Box::new(ast::LetExpr { pattern: ast::Pattern::Identifier("inner".to_string()), type_annotation: None, value: Some(Box::new(Expr::Reference { expr: Box::new(Expr::Identifier("x".to_string(), span())), is_mutable: false, span: span() })), body: Box::new(Expr::Identifier("inner".to_string(), span())) }), span())), ownership: OwnershipModifier::Inferred }, span()),
2164 Statement::Assignment(ast::Assignment { pattern: DestructurePattern::Identifier("x".to_string(), span()), value: Expr::Literal(ast::Literal::Int(2), span()) }, span()),
2165 Statement::Expression(Expr::Identifier("shared".to_string(), span()), span()),
2166 ];
2167 let lowering = lower_function_detailed("test", &[], &body, span());
2168 assert!(!lowering.had_fallbacks);
2169 let analysis = solver::analyze(&lowering.mir, &Default::default());
2170 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
2171 }
2172
2173 #[test]
2174 fn test_lowered_if_expression_with_block_branches_stays_supported() {
2175 let block_branch = |borrow_name: &str| {
2176 Expr::Block(ast::BlockExpr { items: vec![ast::BlockItem::Expression(Expr::Reference { expr: Box::new(Expr::Identifier(borrow_name.to_string(), span())), is_mutable: false, span: span() })] }, span())
2177 };
2178 let body = vec![
2179 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: true, pattern: DestructurePattern::Identifier("x".to_string(), span()), type_annotation: None, value: Some(Expr::Literal(ast::Literal::Int(1), span())), ownership: OwnershipModifier::Inferred }, span()),
2180 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("flag".to_string(), span()), type_annotation: None, value: Some(Expr::Literal(ast::Literal::Bool(true), span())), ownership: OwnershipModifier::Inferred }, span()),
2181 Statement::VariableDecl(ast::VariableDecl { kind: VarKind::Let, is_mut: false, pattern: DestructurePattern::Identifier("shared".to_string(), span()), type_annotation: None, value: Some(Expr::Conditional { condition: Box::new(Expr::Identifier("flag".to_string(), span())), then_expr: Box::new(block_branch("x")), else_expr: Some(Box::new(block_branch("x"))), span: span() }), ownership: OwnershipModifier::Inferred }, span()),
2182 Statement::Assignment(ast::Assignment { pattern: DestructurePattern::Identifier("x".to_string(), span()), value: Expr::Literal(ast::Literal::Int(2), span()) }, span()),
2183 ];
2184 let lowering = lower_function_detailed("test", &[], &body, span());
2185 assert!(!lowering.had_fallbacks);
2186 let analysis = solver::analyze(&lowering.mir, &Default::default());
2187 assert!(analysis.errors.is_empty());
2188 }
2189
2190 #[test]
2191 fn test_lowered_async_let_exclusive_ref_task_boundary_is_visible_to_solver() {
2192 let lowering = lower_parsed_function(r#"
2193 async function test() {
2194 let mut x = 1
2195 async let fut = &mut x
2196 }
2197 "#);
2198 assert!(!lowering.had_fallbacks);
2199 let analysis = solver::analyze(&lowering.mir, &Default::default());
2200 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ExclusiveRefAcrossTaskBoundary));
2201 }
2202
2203 #[test]
2204 fn test_lowered_async_let_nested_ref_binding_task_boundary_is_visible_to_solver() {
2205 let lowering = lower_parsed_function(r#"
2206 async function test() {
2207 let mut x = 1
2208 async let fut = {
2209 let r = &mut x
2210 r
2211 }
2212 }
2213 "#);
2214 assert!(!lowering.had_fallbacks);
2215 let analysis = solver::analyze(&lowering.mir, &Default::default());
2216 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ExclusiveRefAcrossTaskBoundary));
2217 }
2218
2219 #[test]
2220 fn test_lowered_async_let_shared_ref_task_boundary_stays_clean() {
2221 let lowering = lower_parsed_function(r#"
2222 async function test() {
2223 let x = 1
2224 async let fut = &x
2225 await fut
2226 }
2227 "#);
2228 assert!(!lowering.had_fallbacks);
2229 let analysis = solver::analyze(&lowering.mir, &Default::default());
2230 assert!(!analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ExclusiveRefAcrossTaskBoundary));
2231 }
2232
2233 #[test]
2234 fn test_lowered_join_exclusive_ref_task_boundary_is_visible_to_solver() {
2235 let lowering = lower_parsed_function(r#"
2236 async function test() {
2237 let mut x = 1
2238 await join all {
2239 &mut x,
2240 2,
2241 }
2242 }
2243 "#);
2244 assert!(!lowering.had_fallbacks);
2245 let analysis = solver::analyze(&lowering.mir, &Default::default());
2246 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ExclusiveRefAcrossTaskBoundary));
2247 }
2248
2249 #[test]
2250 fn test_lowered_async_scope_with_async_let_stays_supported() {
2251 let lowering = lower_parsed_function(r#"
2252 async function test() {
2253 let x = 1
2254 async scope {
2255 async let fut = &x
2256 await fut
2257 }
2258 }
2259 "#);
2260 assert!(!lowering.had_fallbacks);
2261 let analysis = solver::analyze(&lowering.mir, &Default::default());
2262 assert!(analysis.errors.is_empty());
2263 }
2264
2265 #[test]
2266 fn test_lowered_closure_capture_of_reference_is_visible_to_solver() {
2267 let lowering = lower_parsed_function(r#"
2268 function test() {
2269 let x = 1
2270 let r = &x
2271 let f = || r
2272 }
2273 "#);
2274 assert!(!lowering.had_fallbacks);
2275 let analysis = solver::analyze(&lowering.mir, &Default::default());
2276 assert!(analysis.errors.is_empty());
2277 }
2278
2279 #[test]
2280 fn test_lowered_returned_array_with_ref_still_errors() {
2281 let lowering = lower_parsed_function(r#"
2282 function test() {
2283 let x = 1
2284 let arr = [&x]
2285 return arr
2286 }
2287 "#);
2288 assert!(!lowering.had_fallbacks);
2289 let analysis = solver::analyze(&lowering.mir, &Default::default());
2290 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ReferenceStoredInArray));
2291 }
2292
2293 #[test]
2294 fn test_lowered_returned_closure_with_ref_still_errors() {
2295 let lowering = lower_parsed_function(r#"
2296 function test() {
2297 let x = 1
2298 let r = &x
2299 let f = || r
2300 return f
2301 }
2302 "#);
2303 assert!(!lowering.had_fallbacks);
2304 let analysis = solver::analyze(&lowering.mir, &Default::default());
2305 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ReferenceEscapeIntoClosure));
2306 }
2307
2308 #[test]
2309 fn test_lowered_closure_capture_of_owned_value_stays_clean() {
2310 let lowering = lower_parsed_function(r#"
2311 function test() {
2312 let x = 1
2313 let f = || x
2314 }
2315 "#);
2316 assert!(!lowering.had_fallbacks);
2317 let analysis = solver::analyze(&lowering.mir, &Default::default());
2318 assert!(analysis.errors.is_empty());
2319 }
2320
2321 #[test]
2322 fn test_lowered_list_comprehension_write_conflict_is_visible_to_solver() {
2323 let lowering = lower_parsed_function(r#"
2324 function test() {
2325 let mut x = 1
2326 let r = &x
2327 let xs = [(x = 2) for y in [1]]
2328 r
2329 }
2330 "#);
2331 assert!(!lowering.had_fallbacks);
2332 let analysis = solver::analyze(&lowering.mir, &Default::default());
2333 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
2334 }
2335
2336 #[test]
2337 fn test_lowered_from_query_write_conflict_is_visible_to_solver() {
2338 let lowering = lower_parsed_function(r#"
2339 function test() {
2340 let mut x = 1
2341 let r = &x
2342 let rows = from y in [1] where (x = 2) > 0 select y
2343 r
2344 }
2345 "#);
2346 assert!(!lowering.had_fallbacks);
2347 let analysis = solver::analyze(&lowering.mir, &Default::default());
2348 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
2349 }
2350
2351 #[test]
2352 fn test_lowered_comptime_expr_stays_supported() {
2353 let lowering = lower_parsed_function(r#"
2354 function test() {
2355 let generated = comptime {
2356 let x = 1
2357 }
2358 }
2359 "#);
2360 assert!(!lowering.had_fallbacks);
2361 }
2362
2363 #[test]
2364 fn test_lowered_comptime_for_expr_stays_supported() {
2365 let lowering = lower_parsed_function(r#"
2366 function test() {
2367 let generated = comptime for f in [1, 2] {
2368 let y = f
2369 }
2370 }
2371 "#);
2372 assert!(!lowering.had_fallbacks);
2373 }
2374
2375 #[test]
2376 fn test_lowered_numeric_literals_preserve_values() {
2377 let body = vec![
2380 Statement::VariableDecl(
2381 ast::VariableDecl {
2382 kind: VarKind::Let,
2383 is_mut: false,
2384 pattern: DestructurePattern::Identifier("a".to_string(), span()),
2385 type_annotation: None,
2386 value: Some(Expr::Literal(ast::Literal::Int(42), span())),
2387 ownership: OwnershipModifier::Inferred,
2388 },
2389 span(),
2390 ),
2391 Statement::VariableDecl(
2392 ast::VariableDecl {
2393 kind: VarKind::Let,
2394 is_mut: false,
2395 pattern: DestructurePattern::Identifier("b".to_string(), span()),
2396 type_annotation: None,
2397 value: Some(Expr::Literal(ast::Literal::Number(2.5), span())),
2398 ownership: OwnershipModifier::Inferred,
2399 },
2400 span(),
2401 ),
2402 Statement::VariableDecl(
2403 ast::VariableDecl {
2404 kind: VarKind::Let,
2405 is_mut: false,
2406 pattern: DestructurePattern::Identifier("c".to_string(), span()),
2407 type_annotation: None,
2408 value: Some(Expr::Literal(ast::Literal::Bool(true), span())),
2409 ownership: OwnershipModifier::Inferred,
2410 },
2411 span(),
2412 ),
2413 ];
2414 let mir = lower_function("test", &[], &body, span());
2415
2416 let mut constants: Vec<MirConstant> = Vec::new();
2418 for block in &mir.blocks {
2419 for stmt in &block.statements {
2420 if let StatementKind::Assign(_, Rvalue::Use(Operand::Constant(c))) = &stmt.kind {
2421 constants.push(c.clone());
2422 }
2423 }
2424 }
2425
2426 assert!(
2428 constants.contains(&MirConstant::Int(42)),
2429 "expected MirConstant::Int(42) in {constants:?}"
2430 );
2431 assert!(
2433 constants.contains(&MirConstant::Float(f64::to_bits(2.5))),
2434 "expected MirConstant::Float(2.5) in {constants:?}"
2435 );
2436 assert!(
2438 constants.contains(&MirConstant::Bool(true)),
2439 "expected MirConstant::Bool(true) in {constants:?}"
2440 );
2441 }
2442
2443 #[test]
2456 fn test_for_range_continue_routes_through_increment_block() {
2457 let mir = lower_parsed_function(
2458 "fn run() {\n for i in 0..3 {\n if i == 1 { continue }\n print(i)\n }\n}",
2459 )
2460 .mir;
2461
2462 let mut header_id: Option<BasicBlockId> = None;
2467 let mut header_true_bb: Option<BasicBlockId> = None;
2468 for block in &mir.blocks {
2469 let has_lt = block.statements.iter().any(|s| {
2470 matches!(
2471 &s.kind,
2472 StatementKind::Assign(_, Rvalue::BinaryOp(BinOp::Lt, _, _))
2473 )
2474 });
2475 if has_lt {
2476 if let TerminatorKind::SwitchBool { true_bb, .. } = &block.terminator.kind {
2477 header_id = Some(block.id);
2478 header_true_bb = Some(*true_bb);
2479 break;
2480 }
2481 }
2482 }
2483 let header_id = header_id.expect("loop header block not found");
2484 let header_true_bb = header_true_bb.expect("loop header SwitchBool true_bb not found");
2485
2486 let increment_target_id = mir
2492 .blocks
2493 .iter()
2494 .find(|b| {
2495 let goes_to_header = matches!(
2496 &b.terminator.kind,
2497 TerminatorKind::Goto(t) if *t == header_id
2498 );
2499 let increments = b.statements.iter().any(|s| {
2500 matches!(
2501 &s.kind,
2502 StatementKind::Assign(_, Rvalue::BinaryOp(BinOp::Add, _, _))
2503 )
2504 });
2505 goes_to_header && increments
2506 })
2507 .map(|b| b.id)
2508 .expect("continue_target block (increment + Goto(header)) not found");
2509
2510 assert_ne!(
2516 header_true_bb, increment_target_id,
2517 "loop body and continue_target collapsed to the same block — \
2518 `continue` would skip the increment and infinite-loop"
2519 );
2520
2521 assert_ne!(
2525 increment_target_id, header_id,
2526 "continue_target block must not equal header (the increment \
2527 must precede the back-jump, not the cond check)"
2528 );
2529 }
2530
2531 #[test]
2557 fn test_shadow_with_same_name_lowers_init_against_outer_binding() {
2558 let body = vec![
2564 Statement::VariableDecl(
2565 ast::VariableDecl {
2566 kind: VarKind::Let,
2567 is_mut: false,
2568 pattern: DestructurePattern::Identifier("x".to_string(), span()),
2569 type_annotation: None,
2570 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
2571 ownership: OwnershipModifier::Inferred,
2572 },
2573 span(),
2574 ),
2575 Statement::VariableDecl(
2576 ast::VariableDecl {
2577 kind: VarKind::Let,
2578 is_mut: false,
2579 pattern: DestructurePattern::Identifier("x".to_string(), span()),
2580 type_annotation: None,
2581 value: Some(Expr::Identifier("x".to_string(), span())),
2582 ownership: OwnershipModifier::Inferred,
2583 },
2584 span(),
2585 ),
2586 ];
2587 let mir = lower_function("test_shadow_same_name", &[], &body, span());
2588 let mut assign_pairs = Vec::new();
2592 for block in mir.blocks.iter() {
2593 for stmt in block.statements.iter() {
2594 if let StatementKind::Assign(dst, Rvalue::Use(operand)) = &stmt.kind {
2595 if let (
2596 Place::Local(dst_slot),
2597 Operand::Copy(Place::Local(src_slot))
2598 | Operand::Move(Place::Local(src_slot))
2599 | Operand::MoveExplicit(Place::Local(src_slot)),
2600 ) = (dst, operand)
2601 {
2602 assign_pairs.push((*dst_slot, *src_slot));
2603 }
2604 }
2605 }
2606 }
2607 let shadow_assign = assign_pairs
2610 .iter()
2611 .find(|(dst, src)| dst != src)
2612 .copied()
2613 .expect("expected at least one Assign with dst != src");
2614 let (shadow_dst, shadow_src) = shadow_assign;
2615 assert_ne!(
2616 shadow_dst, shadow_src,
2617 "shadow `let x = x` must read OUTER x slot (not self-referential)"
2618 );
2619 }
2620
2621 #[test]
2622 fn test_shadow_in_loop_threads_outer_value() {
2623 let body = vec![
2633 Statement::VariableDecl(
2634 ast::VariableDecl {
2635 kind: VarKind::Let,
2636 is_mut: true,
2637 pattern: DestructurePattern::Identifier("acc".to_string(), span()),
2638 type_annotation: None,
2639 value: Some(Expr::Literal(ast::Literal::Int(0), span())),
2640 ownership: OwnershipModifier::Inferred,
2641 },
2642 span(),
2643 ),
2644 ];
2645 let mir = lower_function("test_shadow_in_loop", &[], &body, span());
2646 assert!(mir.num_locals >= 1);
2648 }
2649
2650 #[test]
2651 fn test_let_x_x_does_not_self_reference_in_mir() {
2652 let body = vec![
2656 Statement::VariableDecl(
2657 ast::VariableDecl {
2658 kind: VarKind::Let,
2659 is_mut: false,
2660 pattern: DestructurePattern::Identifier("x".to_string(), span()),
2661 type_annotation: None,
2662 value: Some(Expr::Literal(ast::Literal::Int(42), span())),
2663 ownership: OwnershipModifier::Inferred,
2664 },
2665 span(),
2666 ),
2667 Statement::VariableDecl(
2668 ast::VariableDecl {
2669 kind: VarKind::Let,
2670 is_mut: false,
2671 pattern: DestructurePattern::Identifier("x".to_string(), span()),
2672 type_annotation: None,
2673 value: Some(Expr::Identifier("x".to_string(), span())),
2674 ownership: OwnershipModifier::Inferred,
2675 },
2676 span(),
2677 ),
2678 ];
2679 let mir = lower_function("test_let_x_x", &[], &body, span());
2680 for block in mir.blocks.iter() {
2683 for stmt in block.statements.iter() {
2684 if let StatementKind::Assign(Place::Local(dst), Rvalue::Use(operand)) =
2685 &stmt.kind
2686 {
2687 if let Operand::Copy(Place::Local(src))
2688 | Operand::Move(Place::Local(src))
2689 | Operand::MoveExplicit(Place::Local(src)) = operand
2690 {
2691 assert_ne!(
2692 dst, src,
2693 "MIR contains self-Use Assign(slot {:?}, Use(slot {:?})) — \
2694 the shadow `let x = x` MUST NOT lower to a self-Assign \
2695 (regression of v0.3 Vec.reduce fold-state JIT divergence)",
2696 dst, src,
2697 );
2698 }
2699 }
2700 }
2701 }
2702 }
2703
2704 #[test]
2705 fn test_non_shadow_let_preserves_outer_lookups() {
2706 let body = vec![
2711 Statement::VariableDecl(
2712 ast::VariableDecl {
2713 kind: VarKind::Let,
2714 is_mut: false,
2715 pattern: DestructurePattern::Identifier("x".to_string(), span()),
2716 type_annotation: None,
2717 value: Some(Expr::Literal(ast::Literal::Int(7), span())),
2718 ownership: OwnershipModifier::Inferred,
2719 },
2720 span(),
2721 ),
2722 Statement::VariableDecl(
2723 ast::VariableDecl {
2724 kind: VarKind::Let,
2725 is_mut: false,
2726 pattern: DestructurePattern::Identifier("y".to_string(), span()),
2727 type_annotation: None,
2728 value: Some(Expr::Identifier("x".to_string(), span())),
2729 ownership: OwnershipModifier::Inferred,
2730 },
2731 span(),
2732 ),
2733 ];
2734 let mir = lower_function("test_non_shadow", &[], &body, span());
2735 let mut all_assigns = Vec::new();
2738 for block in mir.blocks.iter() {
2739 for stmt in block.statements.iter() {
2740 if let StatementKind::Assign(Place::Local(dst), Rvalue::Use(operand)) =
2741 &stmt.kind
2742 {
2743 if let Operand::Copy(Place::Local(src))
2744 | Operand::Move(Place::Local(src))
2745 | Operand::MoveExplicit(Place::Local(src)) = operand
2746 {
2747 all_assigns.push((*dst, *src));
2748 }
2749 }
2750 }
2751 }
2752 let cross_slot_assign = all_assigns
2755 .iter()
2756 .find(|(dst, src)| dst != src)
2757 .copied()
2758 .expect("expected slot-to-slot Use Assign for `let y = x`");
2759 let (y_dst, x_src) = cross_slot_assign;
2760 assert_ne!(y_dst, x_src, "y must be a fresh slot");
2761 }
2762}