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}
115
116#[derive(Debug)]
117pub struct MirLoweringResult {
118 pub mir: MirFunction,
119 pub had_fallbacks: bool,
120 pub fallback_spans: Vec<Span>,
123 pub binding_infos: Vec<LoweredBindingInfo>,
124 pub field_names: HashMap<FieldIdx, String>,
126 pub all_local_names: HashSet<String>,
129}
130
131impl MirBuilder {
136 pub fn new(name: String, span: Span) -> Self {
137 let return_slot = SlotId(0);
138 MirBuilder {
139 name,
140 blocks: Vec::new(),
141 current_stmts: Vec::new(),
142 current_block: BasicBlockId(0),
143 current_block_finished: false,
144 next_block_id: 1,
145 next_local: 1,
146 return_slot,
147 next_point: 0,
148 next_loan: 0,
149 locals: vec![MirLocalRecord {
150 name: "__mir_return".to_string(),
151 type_info: LocalTypeInfo::Unknown,
152 binding_info: None,
153 }],
154 local_slots: HashMap::new(),
155 field_indices: HashMap::new(),
156 next_field_idx: 0,
157 param_slots: Vec::new(),
158 param_reference_kinds: Vec::new(),
159 scope_bindings: vec![Vec::new()],
160 loop_contexts: Vec::new(),
161 task_boundary_capture_scopes: Vec::new(),
162 async_scope_depth: 0,
163 exit_block: None,
164 span,
165 fallback_spans: Vec::new(),
166 }
167 }
168
169 pub fn alloc_local(&mut self, name: String, type_info: LocalTypeInfo) -> SlotId {
171 self.alloc_local_with_binding(name, type_info, None)
172 }
173
174 pub(super) fn alloc_local_binding(
175 &mut self,
176 name: String,
177 type_info: LocalTypeInfo,
178 binding_metadata: BindingMetadata,
179 ) -> SlotId {
180 self.alloc_local_with_binding(name, type_info, Some(binding_metadata))
181 }
182
183 fn alloc_local_with_binding(
184 &mut self,
185 name: String,
186 type_info: LocalTypeInfo,
187 binding_metadata: Option<BindingMetadata>,
188 ) -> SlotId {
189 let slot = SlotId(self.next_local);
190 self.next_local += 1;
191 let binding_info = binding_metadata.map(|binding_metadata| LoweredBindingInfo {
192 slot,
193 name: name.clone(),
194 declaration_span: binding_metadata.declaration_span,
195 enforce_immutable_assignment: binding_metadata.enforce_immutable_assignment,
196 is_explicit_let: binding_metadata.is_explicit_let,
197 is_const: binding_metadata.is_const,
198 initialization_point: None,
199 });
200 self.locals.push(MirLocalRecord {
201 name,
202 type_info,
203 binding_info,
204 });
205 if let Some(local) = self.locals.last()
206 && !local.name.starts_with("__mir_")
207 {
208 self.bind_named_local(local.name.clone(), slot);
209 }
210 slot
211 }
212
213 pub fn alloc_temp(&mut self, type_info: LocalTypeInfo) -> SlotId {
215 let name = format!("__mir_tmp{}", self.next_local);
216 self.alloc_local(name, type_info)
217 }
218
219 fn add_param(
221 &mut self,
222 name: String,
223 type_info: LocalTypeInfo,
224 reference_kind: Option<BorrowKind>,
225 binding_metadata: Option<BindingMetadata>,
226 ) -> SlotId {
227 let slot = self.alloc_local_with_binding(name, type_info, binding_metadata);
228 self.param_slots.push(slot);
229 self.param_reference_kinds.push(reference_kind);
230 slot
231 }
232
233 pub fn lookup_local(&self, name: &str) -> Option<SlotId> {
235 self.local_slots.get(name).copied()
236 }
237
238 pub fn visible_named_locals(&self) -> Vec<String> {
239 self.local_slots
240 .keys()
241 .filter(|name| !name.starts_with("__mir_"))
242 .cloned()
243 .collect()
244 }
245
246 pub fn field_idx(&mut self, property: &str) -> FieldIdx {
248 if let Some(idx) = self.field_indices.get(property).copied() {
249 return idx;
250 }
251 let idx = FieldIdx(self.next_field_idx);
252 self.next_field_idx += 1;
253 self.field_indices.insert(property.to_string(), idx);
254 idx
255 }
256
257 pub fn return_slot(&self) -> SlotId {
258 self.return_slot
259 }
260
261 pub fn set_exit_block(&mut self, block: BasicBlockId) {
262 self.exit_block = Some(block);
263 }
264
265 pub fn exit_block(&self) -> BasicBlockId {
266 self.exit_block
267 .expect("MIR builder exit block should be initialized before lowering")
268 }
269
270 pub fn push_scope(&mut self) {
271 self.scope_bindings.push(Vec::new());
272 }
273
274 pub fn pop_scope(&mut self) {
275 if self.scope_bindings.len() <= 1 {
276 return;
277 }
278 if let Some(bindings) = self.scope_bindings.pop() {
279 for (name, previous_slot) in bindings.into_iter().rev() {
280 if let Some(slot) = previous_slot {
281 self.local_slots.insert(name, slot);
282 } else {
283 self.local_slots.remove(&name);
284 }
285 }
286 }
287 }
288
289 fn bind_named_local(&mut self, name: String, slot: SlotId) {
290 if let Some(scope) = self.scope_bindings.last_mut()
291 && !scope.iter().any(|(existing, _)| existing == &name)
292 {
293 scope.push((name.clone(), self.local_slots.get(&name).copied()));
294 }
295 self.local_slots.insert(name, slot);
296 }
297
298 pub fn mark_fallback(&mut self) {
299 self.fallback_spans.push(self.span);
301 }
302
303 pub fn mark_fallback_at(&mut self, span: Span) {
304 self.fallback_spans.push(span);
305 }
306
307 pub fn had_fallbacks(&self) -> bool {
308 !self.fallback_spans.is_empty()
309 }
310
311 pub fn push_loop(
312 &mut self,
313 break_block: BasicBlockId,
314 continue_block: BasicBlockId,
315 break_value_slot: Option<SlotId>,
316 ) {
317 self.loop_contexts.push(MirLoopContext {
318 break_block,
319 continue_block,
320 break_value_slot,
321 });
322 }
323
324 pub fn pop_loop(&mut self) {
325 self.loop_contexts.pop();
326 }
327
328 pub(super) fn current_loop(&self) -> Option<MirLoopContext> {
329 self.loop_contexts.last().copied()
330 }
331
332 pub fn push_task_boundary_capture_scope(&mut self) {
333 self.task_boundary_capture_scopes
334 .push(TaskBoundaryCaptureScope {
335 outer_locals_cutoff: self.next_local,
336 operands: Vec::new(),
337 });
338 }
339
340 pub fn pop_task_boundary_capture_scope(&mut self) -> Vec<Operand> {
341 self.task_boundary_capture_scopes
342 .pop()
343 .map(|scope| scope.operands)
344 .unwrap_or_default()
345 }
346
347 pub fn record_task_boundary_operand(&mut self, operand: Operand) {
348 for scope in &mut self.task_boundary_capture_scopes {
349 if !helpers::operand_crosses_task_boundary(scope.outer_locals_cutoff, &operand) {
350 continue;
351 }
352 if !scope.operands.contains(&operand) {
353 scope.operands.push(operand.clone());
354 }
355 }
356 }
357
358 pub fn record_task_boundary_reference_capture(
359 &mut self,
360 reference_slot: SlotId,
361 borrowed_place: &Place,
362 ) {
363 let reference_operand = Operand::Copy(Place::Local(reference_slot));
364 for scope in &mut self.task_boundary_capture_scopes {
365 if borrowed_place.root_local().0 >= scope.outer_locals_cutoff {
366 continue;
367 }
368 if !scope.operands.contains(&reference_operand) {
369 scope.operands.push(reference_operand.clone());
370 }
371 }
372 }
373
374 pub fn next_point(&mut self) -> Point {
376 let p = Point(self.next_point);
377 self.next_point += 1;
378 p
379 }
380
381 pub fn next_loan(&mut self) -> LoanId {
383 let l = LoanId(self.next_loan);
384 self.next_loan += 1;
385 l
386 }
387
388 pub fn new_block(&mut self) -> BasicBlockId {
390 let id = BasicBlockId(self.next_block_id);
391 self.next_block_id += 1;
392 id
393 }
394
395 pub fn push_stmt(&mut self, kind: StatementKind, span: Span) -> Point {
397 let point = self.next_point();
398 self.current_stmts.push(MirStatement { kind, span, point });
399 point
400 }
401
402 pub fn record_binding_initialization(&mut self, slot: SlotId, point: Point) {
403 if let Some(local) = self.locals.get_mut(slot.0 as usize)
404 && let Some(binding_info) = local.binding_info.as_mut()
405 {
406 binding_info.initialization_point = Some(point);
407 }
408 }
409
410 pub fn finish_block(&mut self, terminator_kind: TerminatorKind, span: Span) {
412 let block = BasicBlock {
413 id: self.current_block,
414 statements: std::mem::take(&mut self.current_stmts),
415 terminator: Terminator {
416 kind: terminator_kind,
417 span,
418 },
419 };
420 self.blocks.push(block);
421 self.current_block_finished = true;
422 }
423
424 pub fn start_block(&mut self, id: BasicBlockId) {
426 self.current_block = id;
427 self.current_stmts.clear();
428 self.current_block_finished = false;
429 }
430
431 pub fn emit_call(
434 &mut self,
435 func: Operand,
436 args: Vec<Operand>,
437 destination: Place,
438 span: Span,
439 ) {
440 let next_bb = self.new_block();
441 self.finish_block(
442 TerminatorKind::Call {
443 func,
444 args,
445 destination,
446 next: next_bb,
447 },
448 span,
449 );
450 self.start_block(next_bb);
451 }
452
453 pub fn build(self) -> MirLoweringResult {
455 let local_types = self
456 .locals
457 .iter()
458 .map(|local| local.type_info.clone())
459 .collect();
460 let binding_infos = self
461 .locals
462 .iter()
463 .filter_map(|local| local.binding_info.clone())
464 .collect();
465 let field_names: HashMap<FieldIdx, String> = self
466 .field_indices
467 .iter()
468 .map(|(name, &idx)| (idx, name.clone()))
469 .collect();
470 let mut blocks = self.blocks;
472 blocks.sort_by_key(|b| b.id.0);
473
474 let had_fallbacks = !self.fallback_spans.is_empty();
475 let fallback_spans = self.fallback_spans;
476 let all_local_names: HashSet<String> = self
477 .locals
478 .iter()
479 .filter(|l| !l.name.starts_with("__mir_"))
480 .map(|l| l.name.clone())
481 .collect();
482
483 MirLoweringResult {
484 mir: MirFunction {
485 name: self.name,
486 blocks,
487 num_locals: self.next_local,
488 param_slots: self.param_slots,
489 param_reference_kinds: self.param_reference_kinds,
490 local_types,
491 span: self.span,
492 },
493 had_fallbacks,
494 fallback_spans,
495 binding_infos,
496 field_names,
497 all_local_names,
498 }
499 }
500}
501
502pub(super) fn immutable_binding_metadata(
507 declaration_span: Span,
508 is_explicit_let: bool,
509 is_const: bool,
510) -> BindingMetadata {
511 BindingMetadata {
512 declaration_span,
513 enforce_immutable_assignment: true,
514 is_explicit_let,
515 is_const,
516 }
517}
518
519pub fn lower_function_detailed(
521 name: &str,
522 params: &[ast::FunctionParameter],
523 body: &[Statement],
524 span: Span,
525) -> MirLoweringResult {
526 let mut builder = MirBuilder::new(name.to_string(), span);
527
528 for param in params {
530 let type_info = if param.is_reference {
531 LocalTypeInfo::NonCopy } else {
533 LocalTypeInfo::Unknown };
535 let reference_kind = if param.is_mut_reference {
536 Some(BorrowKind::Exclusive)
537 } else if param.is_reference {
538 Some(BorrowKind::Shared)
539 } else {
540 None
541 };
542 let binding_metadata = if param.is_const {
543 Some(immutable_binding_metadata(param.span(), false, true))
544 } else if matches!(reference_kind, Some(BorrowKind::Shared)) {
545 Some(immutable_binding_metadata(param.span(), false, false))
546 } else {
547 None
548 };
549 if let Some(param_name) = param.simple_name() {
550 builder.add_param(
551 param_name.to_string(),
552 type_info,
553 reference_kind,
554 binding_metadata,
555 );
556 } else {
557 let slot = builder.add_param(
558 format!("__mir_param{}", builder.param_slots.len()),
559 type_info,
560 reference_kind,
561 None,
562 );
563 stmt::lower_destructure_bindings_from_place(
564 &mut builder,
565 ¶m.pattern,
566 &Place::Local(slot),
567 param.span(),
568 binding_metadata,
569 );
570 }
571 }
572
573 let exit_block = builder.new_block();
575 builder.set_exit_block(exit_block);
576
577 stmt::lower_statements(&mut builder, body, exit_block);
579
580 if !builder.current_block_finished {
582 builder.finish_block(TerminatorKind::Goto(exit_block), span);
583 }
584
585 builder.start_block(exit_block);
587 builder.finish_block(TerminatorKind::Return, span);
588
589 builder.build()
590}
591
592pub fn lower_function(
594 name: &str,
595 params: &[ast::FunctionParameter],
596 body: &[Statement],
597 span: Span,
598) -> MirFunction {
599 lower_function_detailed(name, params, body, span).mir
600}
601
602pub fn compute_mutability_errors(lowering: &MirLoweringResult) -> Vec<MutabilityError> {
603 let tracked_bindings: HashMap<SlotId, &LoweredBindingInfo> = lowering
604 .binding_infos
605 .iter()
606 .filter(|binding| binding.enforce_immutable_assignment)
607 .map(|binding| (binding.slot, binding))
608 .collect();
609 let mut errors = Vec::new();
610
611 for block in &lowering.mir.blocks {
612 for stmt in &block.statements {
613 let StatementKind::Assign(place, _) = &stmt.kind else {
614 continue;
615 };
616 let root = place.root_local();
617 let Some(binding) = tracked_bindings.get(&root) else {
618 continue;
619 };
620 let is_declaration_init = matches!(place, Place::Local(slot) if *slot == root)
621 && binding.initialization_point == Some(stmt.point);
622 if is_declaration_init {
623 continue;
624 }
625 errors.push(MutabilityError {
626 span: stmt.span,
627 variable_name: binding.name.clone(),
628 declaration_span: binding.declaration_span,
629 is_explicit_let: binding.is_explicit_let,
630 is_const: binding.is_const,
631 });
632 }
633 }
634
635 errors
636}
637
638#[cfg(test)]
643mod tests {
644 use super::*;
645 use crate::mir::analysis::BorrowErrorKind;
646 use crate::mir::cfg::ControlFlowGraph;
647 use crate::mir::liveness;
648 use crate::mir::solver;
649 use shape_ast::ast::{self, DestructurePattern, Expr, OwnershipModifier, VarKind};
650
651 fn span() -> Span {
652 Span { start: 0, end: 1 }
653 }
654
655 fn lower_parsed_function(code: &str) -> MirLoweringResult {
656 let program = shape_ast::parser::parse_program(code).expect("parse failed");
657 let func = match &program.items[0] {
658 ast::Item::Function(func, _) => func,
659 _ => panic!("expected function item"),
660 };
661 lower_function_detailed(&func.name, &func.params, &func.body, func.name_span)
662 }
663
664 #[test]
665 fn test_lower_empty_function() {
666 let mir = lower_function("empty", &[], &[], span());
667 assert_eq!(mir.name, "empty");
668 assert!(mir.blocks.len() >= 2); assert_eq!(mir.num_locals, 1);
670 }
671
672 #[test]
673 fn test_lower_simple_var_decl() {
674 let body = vec![Statement::VariableDecl(
675 ast::VariableDecl {
676 kind: VarKind::Let,
677 is_mut: false,
678 pattern: DestructurePattern::Identifier("x".to_string(), span()),
679 type_annotation: None,
680 value: Some(Expr::Literal(ast::Literal::Int(42), span())),
681 ownership: OwnershipModifier::Inferred,
682 },
683 span(),
684 )];
685 let mir = lower_function("test", &[], &body, span());
686 assert!(mir.num_locals >= 1); assert!(mir.blocks.len() >= 2);
689 }
690
691 #[test]
692 fn test_compute_mutability_errors_ignores_binding_initializer() {
693 let lowering = lower_parsed_function(
694 r#"
695 function keep() {
696 let x = 1
697 x
698 }
699 "#,
700 );
701 let errors = compute_mutability_errors(&lowering);
702 assert!(
703 errors.is_empty(),
704 "declaration initializer should not be reported as a mutability error: {:?}",
705 errors
706 );
707 }
708
709 #[test]
710 fn test_compute_mutability_errors_flags_immutable_let_reassignment() {
711 let lowering = lower_parsed_function(
712 r#"
713 function mutate() {
714 let x = 1
715 x = 2
716 x
717 }
718 "#,
719 );
720 let errors = compute_mutability_errors(&lowering);
721 assert_eq!(
722 errors.len(),
723 1,
724 "expected one mutability error, got {errors:?}"
725 );
726 assert_eq!(errors[0].variable_name, "x");
727 assert!(errors[0].is_explicit_let);
728 }
729
730 #[test]
731 fn test_compute_mutability_errors_flags_const_reassignment() {
732 let lowering = lower_parsed_function(
733 r#"
734 function mutate() {
735 const x = 1
736 x = 2
737 x
738 }
739 "#,
740 );
741 let errors = compute_mutability_errors(&lowering);
742 assert_eq!(
743 errors.len(),
744 1,
745 "expected one mutability error, got {errors:?}"
746 );
747 assert_eq!(errors[0].variable_name, "x");
748 assert!(errors[0].is_const);
749 }
750
751 #[test]
752 fn test_compute_mutability_errors_flags_shared_ref_param_write() {
753 let lowering = lower_parsed_function(
754 r#"
755 function mutate(&x) {
756 x = 2
757 x
758 }
759 "#,
760 );
761 let errors = compute_mutability_errors(&lowering);
762 assert_eq!(
763 errors.len(),
764 1,
765 "expected one mutability error, got {errors:?}"
766 );
767 assert_eq!(errors[0].variable_name, "x");
768 assert!(!errors[0].is_explicit_let);
769 }
770
771 #[test]
772 fn test_compute_mutability_errors_flags_const_param_write() {
773 let lowering = lower_parsed_function(
774 r#"
775 function mutate(const x) {
776 x = 2
777 x
778 }
779 "#,
780 );
781 let errors = compute_mutability_errors(&lowering);
782 assert_eq!(
783 errors.len(),
784 1,
785 "expected one mutability error, got {errors:?}"
786 );
787 assert_eq!(errors[0].variable_name, "x");
788 assert!(errors[0].is_const);
789 }
790
791 #[test]
792 fn test_lower_with_liveness() {
793 let body = vec![
795 Statement::VariableDecl(
796 ast::VariableDecl {
797 kind: VarKind::Let,
798 is_mut: false,
799 pattern: DestructurePattern::Identifier("x".to_string(), span()),
800 type_annotation: None,
801 value: Some(Expr::Literal(
802 ast::Literal::String("hi".to_string()),
803 span(),
804 )),
805 ownership: OwnershipModifier::Inferred,
806 },
807 span(),
808 ),
809 Statement::VariableDecl(
810 ast::VariableDecl {
811 kind: VarKind::Let,
812 is_mut: false,
813 pattern: DestructurePattern::Identifier("y".to_string(), span()),
814 type_annotation: None,
815 value: Some(Expr::Identifier("x".to_string(), span())),
816 ownership: OwnershipModifier::Inferred,
817 },
818 span(),
819 ),
820 Statement::VariableDecl(
821 ast::VariableDecl {
822 kind: VarKind::Let,
823 is_mut: false,
824 pattern: DestructurePattern::Identifier("kept".to_string(), span()),
825 type_annotation: None,
826 value: Some(Expr::Identifier("shared".to_string(), span())),
827 ownership: OwnershipModifier::Inferred,
828 },
829 span(),
830 ),
831 ];
832 let mir = lower_function("test", &[], &body, span());
833 let cfg = ControlFlowGraph::build(&mir);
834 let _liveness = liveness::compute_liveness(&mir, &cfg);
835 }
837
838 #[test]
839 fn test_lower_reference_to_identifier_borrows_original_local() {
840 let body = vec![
841 Statement::VariableDecl(
842 ast::VariableDecl {
843 kind: VarKind::Let,
844 is_mut: false,
845 pattern: DestructurePattern::Identifier("x".to_string(), span()),
846 type_annotation: None,
847 value: Some(Expr::Literal(
848 ast::Literal::String("hi".to_string()),
849 span(),
850 )),
851 ownership: OwnershipModifier::Inferred,
852 },
853 span(),
854 ),
855 Statement::VariableDecl(
856 ast::VariableDecl {
857 kind: VarKind::Let,
858 is_mut: false,
859 pattern: DestructurePattern::Identifier("r".to_string(), span()),
860 type_annotation: None,
861 value: Some(Expr::Reference {
862 expr: Box::new(Expr::Identifier("x".to_string(), span())),
863 is_mutable: false,
864 span: span(),
865 }),
866 ownership: OwnershipModifier::Inferred,
867 },
868 span(),
869 ),
870 ];
871 let mir = lower_function("test", &[], &body, span());
872 let borrow_place = mir
873 .blocks
874 .iter()
875 .flat_map(|block| block.statements.iter())
876 .find_map(|stmt| match &stmt.kind {
877 StatementKind::Assign(_, Rvalue::Borrow(_, place)) => Some(place.clone()),
878 _ => None,
879 })
880 .expect("expected borrow statement");
881 assert_eq!(borrow_place, Place::Local(SlotId(1)));
882 }
883
884 #[test]
885 fn test_lowered_local_borrow_conflict_is_visible_to_solver() {
886 let body = vec![
887 Statement::VariableDecl(
888 ast::VariableDecl {
889 kind: VarKind::Let,
890 is_mut: true,
891 pattern: DestructurePattern::Identifier("x".to_string(), span()),
892 type_annotation: None,
893 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
894 ownership: OwnershipModifier::Inferred,
895 },
896 span(),
897 ),
898 Statement::VariableDecl(
899 ast::VariableDecl {
900 kind: VarKind::Let,
901 is_mut: false,
902 pattern: DestructurePattern::Identifier("shared".to_string(), span()),
903 type_annotation: None,
904 value: Some(Expr::Reference {
905 expr: Box::new(Expr::Identifier("x".to_string(), span())),
906 is_mutable: false,
907 span: span(),
908 }),
909 ownership: OwnershipModifier::Inferred,
910 },
911 span(),
912 ),
913 Statement::VariableDecl(
914 ast::VariableDecl {
915 kind: VarKind::Let,
916 is_mut: false,
917 pattern: DestructurePattern::Identifier("exclusive".to_string(), span()),
918 type_annotation: None,
919 value: Some(Expr::Reference {
920 expr: Box::new(Expr::Identifier("x".to_string(), span())),
921 is_mutable: true,
922 span: span(),
923 }),
924 ownership: OwnershipModifier::Inferred,
925 },
926 span(),
927 ),
928 Statement::Return(Some(Expr::Identifier("shared".to_string(), span())), span()),
929 ];
930 let mir = lower_function("test", &[], &body, span());
931 let analysis = solver::analyze(&mir, &Default::default());
932 assert!(
933 analysis
934 .errors
935 .iter()
936 .any(|error| error.kind == BorrowErrorKind::ConflictSharedExclusive),
937 "expected shared/exclusive conflict, got {:?}",
938 analysis.errors
939 );
940 }
941
942 #[test]
943 fn test_lowered_property_borrows_preserve_disjoint_places() {
944 let body = vec![
945 Statement::VariableDecl(
946 ast::VariableDecl {
947 kind: VarKind::Let,
948 is_mut: true,
949 pattern: DestructurePattern::Identifier("pair".to_string(), span()),
950 type_annotation: None,
951 value: Some(Expr::Literal(ast::Literal::Int(0), span())),
952 ownership: OwnershipModifier::Inferred,
953 },
954 span(),
955 ),
956 Statement::VariableDecl(
957 ast::VariableDecl {
958 kind: VarKind::Let,
959 is_mut: false,
960 pattern: DestructurePattern::Identifier("left".to_string(), span()),
961 type_annotation: None,
962 value: Some(Expr::Reference {
963 expr: Box::new(Expr::PropertyAccess {
964 object: Box::new(Expr::Identifier("pair".to_string(), span())),
965 property: "left".to_string(),
966 optional: false,
967 span: span(),
968 }),
969 is_mutable: true,
970 span: span(),
971 }),
972 ownership: OwnershipModifier::Inferred,
973 },
974 span(),
975 ),
976 Statement::VariableDecl(
977 ast::VariableDecl {
978 kind: VarKind::Let,
979 is_mut: false,
980 pattern: DestructurePattern::Identifier("right".to_string(), span()),
981 type_annotation: None,
982 value: Some(Expr::Reference {
983 expr: Box::new(Expr::PropertyAccess {
984 object: Box::new(Expr::Identifier("pair".to_string(), span())),
985 property: "right".to_string(),
986 optional: false,
987 span: span(),
988 }),
989 is_mutable: true,
990 span: span(),
991 }),
992 ownership: OwnershipModifier::Inferred,
993 },
994 span(),
995 ),
996 Statement::VariableDecl(
997 ast::VariableDecl {
998 kind: VarKind::Let,
999 is_mut: false,
1000 pattern: DestructurePattern::Identifier("kept".to_string(), span()),
1001 type_annotation: None,
1002 value: Some(Expr::Identifier("shared".to_string(), span())),
1003 ownership: OwnershipModifier::Inferred,
1004 },
1005 span(),
1006 ),
1007 ];
1008 let mir = lower_function("test", &[], &body, span());
1009 let analysis = solver::analyze(&mir, &Default::default());
1010 assert!(
1011 analysis.errors.is_empty(),
1012 "disjoint field borrows should not conflict, got {:?}",
1013 analysis.errors
1014 );
1015 }
1016
1017 #[test]
1018 fn test_lowered_write_while_borrowed_is_visible_to_solver() {
1019 let body = vec![
1020 Statement::VariableDecl(
1021 ast::VariableDecl {
1022 kind: VarKind::Let,
1023 is_mut: true,
1024 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1025 type_annotation: None,
1026 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
1027 ownership: OwnershipModifier::Inferred,
1028 },
1029 span(),
1030 ),
1031 Statement::VariableDecl(
1032 ast::VariableDecl {
1033 kind: VarKind::Let,
1034 is_mut: false,
1035 pattern: DestructurePattern::Identifier("shared".to_string(), span()),
1036 type_annotation: None,
1037 value: Some(Expr::Reference {
1038 expr: Box::new(Expr::Identifier("x".to_string(), span())),
1039 is_mutable: false,
1040 span: span(),
1041 }),
1042 ownership: OwnershipModifier::Inferred,
1043 },
1044 span(),
1045 ),
1046 Statement::Assignment(
1047 ast::Assignment {
1048 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1049 value: Expr::Literal(ast::Literal::Int(2), span()),
1050 },
1051 span(),
1052 ),
1053 Statement::Expression(Expr::Identifier("shared".to_string(), span()), span()),
1054 ];
1055 let mir = lower_function("test", &[], &body, span());
1056 let analysis = solver::analyze(&mir, &Default::default());
1057 assert!(
1058 analysis
1059 .errors
1060 .iter()
1061 .any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed),
1062 "expected write-while-borrowed error, got {:?}",
1063 analysis.errors
1064 );
1065 }
1066
1067 #[test]
1068 fn test_lowered_read_while_exclusive_borrow_is_visible_to_solver() {
1069 let body = vec![
1070 Statement::VariableDecl(
1071 ast::VariableDecl {
1072 kind: VarKind::Let,
1073 is_mut: true,
1074 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1075 type_annotation: None,
1076 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
1077 ownership: OwnershipModifier::Inferred,
1078 },
1079 span(),
1080 ),
1081 Statement::VariableDecl(
1082 ast::VariableDecl {
1083 kind: VarKind::Let,
1084 is_mut: false,
1085 pattern: DestructurePattern::Identifier("exclusive".to_string(), span()),
1086 type_annotation: None,
1087 value: Some(Expr::Reference {
1088 expr: Box::new(Expr::Identifier("x".to_string(), span())),
1089 is_mutable: true,
1090 span: span(),
1091 }),
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("copy".to_string(), span()),
1101 type_annotation: None,
1102 value: Some(Expr::Identifier("x".to_string(), span())),
1103 ownership: OwnershipModifier::Inferred,
1104 },
1105 span(),
1106 ),
1107 Statement::Expression(Expr::Identifier("exclusive".to_string(), span()), span()),
1108 ];
1109 let mir = lower_function("test", &[], &body, span());
1110 let analysis = solver::analyze(&mir, &Default::default());
1111 assert!(
1112 analysis
1113 .errors
1114 .iter()
1115 .any(|error| error.kind == BorrowErrorKind::ReadWhileExclusivelyBorrowed),
1116 "expected read-while-exclusive error, got {:?}",
1117 analysis.errors
1118 );
1119 }
1120
1121 #[test]
1122 fn test_lowered_returned_ref_alias_is_visible_to_solver() {
1123 let body = vec![
1124 Statement::VariableDecl(
1125 ast::VariableDecl {
1126 kind: VarKind::Let,
1127 is_mut: false,
1128 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1129 type_annotation: None,
1130 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
1131 ownership: OwnershipModifier::Inferred,
1132 },
1133 span(),
1134 ),
1135 Statement::VariableDecl(
1136 ast::VariableDecl {
1137 kind: VarKind::Let,
1138 is_mut: false,
1139 pattern: DestructurePattern::Identifier("r".to_string(), span()),
1140 type_annotation: None,
1141 value: Some(Expr::Reference {
1142 expr: Box::new(Expr::Identifier("x".to_string(), span())),
1143 is_mutable: false,
1144 span: span(),
1145 }),
1146 ownership: OwnershipModifier::Inferred,
1147 },
1148 span(),
1149 ),
1150 Statement::VariableDecl(
1151 ast::VariableDecl {
1152 kind: VarKind::Let,
1153 is_mut: false,
1154 pattern: DestructurePattern::Identifier("alias".to_string(), span()),
1155 type_annotation: None,
1156 value: Some(Expr::Identifier("r".to_string(), span())),
1157 ownership: OwnershipModifier::Inferred,
1158 },
1159 span(),
1160 ),
1161 Statement::Return(Some(Expr::Identifier("alias".to_string(), span())), span()),
1162 ];
1163 let mir = lower_function("test", &[], &body, span());
1164 let analysis = solver::analyze(&mir, &Default::default());
1165 assert!(
1166 analysis
1167 .errors
1168 .iter()
1169 .any(|error| error.kind == BorrowErrorKind::ReferenceEscape),
1170 "expected reference-escape error, got {:?}",
1171 analysis.errors
1172 );
1173 }
1174
1175 #[test]
1176 fn test_lowered_array_direct_ref_escape_is_visible_to_solver() {
1177 let lowering = lower_parsed_function(
1178 r#"
1179 function test() {
1180 let x = 1
1181 let arr = [&x]
1182 }
1183 "#,
1184 );
1185 assert!(!lowering.had_fallbacks);
1186 let analysis = solver::analyze(&lowering.mir, &Default::default());
1187 assert!(analysis.errors.is_empty());
1188 }
1189
1190 #[test]
1191 fn test_lowered_array_indirect_ref_escape_is_visible_to_solver() {
1192 let lowering = lower_parsed_function(
1193 r#"
1194 function test() {
1195 let x = 1
1196 let r = &x
1197 let arr = [r]
1198 }
1199 "#,
1200 );
1201 assert!(!lowering.had_fallbacks);
1202 let analysis = solver::analyze(&lowering.mir, &Default::default());
1203 assert!(analysis.errors.is_empty());
1204 }
1205
1206 #[test]
1207 fn test_lowered_object_direct_ref_escape_is_visible_to_solver() {
1208 let lowering = lower_parsed_function(
1209 r#"
1210 function test() {
1211 let x = 1
1212 let obj = { value: &x }
1213 }
1214 "#,
1215 );
1216 assert!(!lowering.had_fallbacks);
1217 let analysis = solver::analyze(&lowering.mir, &Default::default());
1218 assert!(analysis.errors.is_empty());
1219 }
1220
1221 #[test]
1222 fn test_lowered_object_indirect_ref_escape_is_visible_to_solver() {
1223 let lowering = lower_parsed_function(
1224 r#"
1225 function test() {
1226 let x = 1
1227 let r = &x
1228 let obj = { value: r }
1229 }
1230 "#,
1231 );
1232 assert!(!lowering.had_fallbacks);
1233 let analysis = solver::analyze(&lowering.mir, &Default::default());
1234 assert!(analysis.errors.is_empty());
1235 }
1236
1237 #[test]
1238 fn test_lowered_struct_direct_ref_escape_is_visible_to_solver() {
1239 let lowering = lower_parsed_function(
1240 r#"
1241 function test() {
1242 let x = 1
1243 let point = Point { value: &x }
1244 }
1245 "#,
1246 );
1247 assert!(!lowering.had_fallbacks);
1248 let analysis = solver::analyze(&lowering.mir, &Default::default());
1249 assert!(analysis.errors.is_empty());
1250 }
1251
1252 #[test]
1253 fn test_lowered_struct_indirect_ref_escape_is_visible_to_solver() {
1254 let lowering = lower_parsed_function(
1255 r#"
1256 function test() {
1257 let x = 1
1258 let r = &x
1259 let point = Point { value: r }
1260 }
1261 "#,
1262 );
1263 assert!(!lowering.had_fallbacks);
1264 let analysis = solver::analyze(&lowering.mir, &Default::default());
1265 assert!(analysis.errors.is_empty());
1266 }
1267
1268 #[test]
1269 fn test_lowered_enum_tuple_direct_ref_escape_is_visible_to_solver() {
1270 let lowering = lower_parsed_function(
1271 r#"
1272 function test() {
1273 let x = 1
1274 let value = Maybe::Some(&x)
1275 }
1276 "#,
1277 );
1278 assert!(!lowering.had_fallbacks);
1279 let analysis = solver::analyze(&lowering.mir, &Default::default());
1280 assert!(analysis.errors.is_empty());
1281 }
1282
1283 #[test]
1284 fn test_lowered_enum_tuple_indirect_ref_escape_is_visible_to_solver() {
1285 let lowering = lower_parsed_function(
1286 r#"
1287 function test() {
1288 let x = 1
1289 let r = &x
1290 let value = Maybe::Some(r)
1291 }
1292 "#,
1293 );
1294 assert!(!lowering.had_fallbacks);
1295 let analysis = solver::analyze(&lowering.mir, &Default::default());
1296 assert!(analysis.errors.is_empty());
1297 }
1298
1299 #[test]
1300 fn test_lowered_enum_struct_direct_ref_escape_is_visible_to_solver() {
1301 let lowering = lower_parsed_function(
1302 r#"
1303 function test() {
1304 let x = 1
1305 let value = Maybe::Err { code: &x }
1306 }
1307 "#,
1308 );
1309 assert!(!lowering.had_fallbacks);
1310 let analysis = solver::analyze(&lowering.mir, &Default::default());
1311 assert!(analysis.errors.is_empty());
1312 }
1313
1314 #[test]
1315 fn test_lowered_enum_struct_indirect_ref_escape_is_visible_to_solver() {
1316 let lowering = lower_parsed_function(
1317 r#"
1318 function test() {
1319 let x = 1
1320 let r = &x
1321 let value = Maybe::Err { code: r }
1322 }
1323 "#,
1324 );
1325 assert!(!lowering.had_fallbacks);
1326 let analysis = solver::analyze(&lowering.mir, &Default::default());
1327 assert!(analysis.errors.is_empty());
1328 }
1329
1330 #[test]
1331 fn test_lowered_use_after_explicit_move_is_visible_to_solver() {
1332 let body = vec![
1333 Statement::VariableDecl(
1334 ast::VariableDecl {
1335 kind: VarKind::Let,
1336 is_mut: false,
1337 pattern: DestructurePattern::Identifier("x".to_string(), span()),
1338 type_annotation: None,
1339 value: Some(Expr::Literal(
1340 ast::Literal::String("hi".to_string()),
1341 span(),
1342 )),
1343 ownership: OwnershipModifier::Inferred,
1344 },
1345 span(),
1346 ),
1347 Statement::VariableDecl(
1348 ast::VariableDecl {
1349 kind: VarKind::Let,
1350 is_mut: false,
1351 pattern: DestructurePattern::Identifier("y".to_string(), span()),
1352 type_annotation: None,
1353 value: Some(Expr::Identifier("x".to_string(), span())),
1354 ownership: OwnershipModifier::Move,
1355 },
1356 span(),
1357 ),
1358 Statement::VariableDecl(
1359 ast::VariableDecl {
1360 kind: VarKind::Let,
1361 is_mut: false,
1362 pattern: DestructurePattern::Identifier("z".to_string(), span()),
1363 type_annotation: None,
1364 value: Some(Expr::Identifier("x".to_string(), span())),
1365 ownership: OwnershipModifier::Inferred,
1366 },
1367 span(),
1368 ),
1369 ];
1370 let mir = lower_function("test", &[], &body, span());
1371 let analysis = solver::analyze(&mir, &Default::default());
1372 assert!(
1373 analysis
1374 .errors
1375 .iter()
1376 .any(|error| error.kind == BorrowErrorKind::UseAfterMove),
1377 "expected use-after-move error, got {:?}",
1378 analysis.errors
1379 );
1380 }
1381
1382 #[test]
1383 fn test_lowered_while_expr_write_while_borrowed_is_visible_to_solver() {
1384 let lowering = lower_parsed_function(
1385 r#"
1386 function test() {
1387 let mut x = 1
1388 let y = while true {
1389 let shared = &x
1390 x = 2
1391 shared
1392 0
1393 }
1394 }
1395 "#,
1396 );
1397 assert!(!lowering.had_fallbacks);
1398 let analysis = solver::analyze(&lowering.mir, &Default::default());
1399 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1400 }
1401
1402 #[test]
1403 fn test_lowered_for_expr_write_while_borrowed_is_visible_to_solver() {
1404 let lowering = lower_parsed_function(
1405 r#"
1406 function test(items) {
1407 let mut x = 1
1408 let y = for item in items {
1409 let shared = &x
1410 x = 2
1411 shared
1412 0
1413 }
1414 }
1415 "#,
1416 );
1417 assert!(!lowering.had_fallbacks);
1418 let analysis = solver::analyze(&lowering.mir, &Default::default());
1419 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1420 }
1421
1422 #[test]
1423 fn test_lowered_loop_expr_break_value_write_while_borrowed_is_visible_to_solver() {
1424 let lowering = lower_parsed_function(
1425 r#"
1426 function test() {
1427 let mut x = 1
1428 let y = loop {
1429 let shared = &x
1430 x = 2
1431 shared
1432 break 0
1433 }
1434 }
1435 "#,
1436 );
1437 assert!(!lowering.had_fallbacks);
1438 let analysis = solver::analyze(&lowering.mir, &Default::default());
1439 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1440 }
1441
1442 #[test]
1443 fn test_lowered_continue_expression_in_while_body_stays_supported() {
1444 let lowering = lower_parsed_function(
1445 r#"
1446 function test(flag) {
1447 let mut x = 1
1448 let y = while flag {
1449 if flag { continue } else { x }
1450 }
1451 }
1452 "#,
1453 );
1454 assert!(!lowering.had_fallbacks);
1455 let analysis = solver::analyze(&lowering.mir, &Default::default());
1456 assert!(analysis.errors.is_empty());
1457 }
1458
1459 #[test]
1460 fn test_lowered_match_expression_write_while_borrowed_is_visible_to_solver() {
1461 let lowering = lower_parsed_function(
1462 r#"
1463 function test(flag) {
1464 let mut x = 1
1465 let y = match flag {
1466 true => {
1467 let shared = &x
1468 x = 2
1469 shared
1470 0
1471 }
1472 _ => 0
1473 }
1474 }
1475 "#,
1476 );
1477 assert!(!lowering.had_fallbacks);
1478 let analysis = solver::analyze(&lowering.mir, &Default::default());
1479 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1480 }
1481
1482 #[test]
1483 fn test_lowered_match_expression_identifier_guard_stays_supported() {
1484 let lowering = lower_parsed_function(
1485 r#"
1486 function test(v) {
1487 let y = match v {
1488 x where x > 0 => x
1489 _ => 0
1490 }
1491 }
1492 "#,
1493 );
1494 assert!(!lowering.had_fallbacks);
1495 let analysis = solver::analyze(&lowering.mir, &Default::default());
1496 assert!(analysis.errors.is_empty());
1497 }
1498
1499 #[test]
1500 fn test_lowered_match_expression_array_pattern_write_while_borrowed_is_visible_to_solver() {
1501 let lowering = lower_parsed_function(
1502 r#"
1503 function test(pair) {
1504 let mut x = 1
1505 let y = match pair {
1506 [left, right] => {
1507 let shared = &x
1508 x = 2
1509 shared
1510 0
1511 }
1512 _ => 0
1513 }
1514 }
1515 "#,
1516 );
1517 assert!(!lowering.had_fallbacks);
1518 let analysis = solver::analyze(&lowering.mir, &Default::default());
1519 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1520 }
1521
1522 #[test]
1523 fn test_lowered_match_expression_object_pattern_write_while_borrowed_is_visible_to_solver() {
1524 let lowering = lower_parsed_function(
1525 r#"
1526 function test(obj) {
1527 let mut x = 1
1528 let y = match obj {
1529 { left: l, right: r } => {
1530 let shared = &x
1531 x = 2
1532 shared
1533 0
1534 }
1535 _ => 0
1536 }
1537 }
1538 "#,
1539 );
1540 assert!(!lowering.had_fallbacks);
1541 let analysis = solver::analyze(&lowering.mir, &Default::default());
1542 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1543 }
1544
1545 #[test]
1546 fn test_lowered_match_expression_constructor_pattern_write_while_borrowed_is_visible_to_solver()
1547 {
1548 let lowering = lower_parsed_function(
1549 r#"
1550 function test(opt) {
1551 let mut x = 1
1552 let y = match opt {
1553 Some(v) => {
1554 let shared = &x
1555 x = 2
1556 shared
1557 0
1558 }
1559 None => 0
1560 }
1561 }
1562 "#,
1563 );
1564 assert!(!lowering.had_fallbacks);
1565 let analysis = solver::analyze(&lowering.mir, &Default::default());
1566 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1567 }
1568
1569 #[test]
1570 fn test_lowered_destructure_var_decl_write_while_borrowed_is_visible_to_solver() {
1571 let lowering = lower_parsed_function(
1572 r#"
1573 function test(pair) {
1574 var [left, right] = pair
1575 let shared = &left
1576 left = 2
1577 shared
1578 }
1579 "#,
1580 );
1581 assert!(!lowering.had_fallbacks);
1582 let analysis = solver::analyze(&lowering.mir, &Default::default());
1583 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1584 }
1585
1586 #[test]
1587 fn test_lowered_destructure_param_write_while_borrowed_is_visible_to_solver() {
1588 let lowering = lower_parsed_function(
1589 r#"
1590 function test([left, right]) {
1591 let mut left_copy = left
1592 let shared = &left_copy
1593 left_copy = 2
1594 shared
1595 }
1596 "#,
1597 );
1598 assert!(!lowering.had_fallbacks);
1599 let analysis = solver::analyze(&lowering.mir, &Default::default());
1600 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1601 }
1602
1603 #[test]
1604 fn test_lowered_destructure_assignment_stays_supported() {
1605 let pair_param = ast::FunctionParameter {
1606 pattern: DestructurePattern::Identifier("pair".to_string(), span()),
1607 is_const: false,
1608 is_reference: false,
1609 is_mut_reference: false,
1610 is_out: false,
1611 type_annotation: None,
1612 default_value: None,
1613 };
1614 let body = vec![
1615 Statement::VariableDecl(
1616 ast::VariableDecl {
1617 kind: VarKind::Let,
1618 is_mut: true,
1619 pattern: DestructurePattern::Identifier("left".to_string(), span()),
1620 type_annotation: None,
1621 value: Some(Expr::Literal(ast::Literal::Int(1), span())),
1622 ownership: OwnershipModifier::Inferred,
1623 },
1624 span(),
1625 ),
1626 Statement::VariableDecl(
1627 ast::VariableDecl {
1628 kind: VarKind::Let,
1629 is_mut: true,
1630 pattern: DestructurePattern::Identifier("right".to_string(), span()),
1631 type_annotation: None,
1632 value: Some(Expr::Literal(ast::Literal::Int(2), span())),
1633 ownership: OwnershipModifier::Inferred,
1634 },
1635 span(),
1636 ),
1637 Statement::Assignment(
1638 ast::Assignment {
1639 pattern: DestructurePattern::Array(vec![
1640 DestructurePattern::Identifier("left".to_string(), span()),
1641 DestructurePattern::Identifier("right".to_string(), span()),
1642 ]),
1643 value: Expr::Identifier("pair".to_string(), span()),
1644 },
1645 span(),
1646 ),
1647 Statement::VariableDecl(
1648 ast::VariableDecl {
1649 kind: VarKind::Let,
1650 is_mut: false,
1651 pattern: DestructurePattern::Identifier("shared".to_string(), span()),
1652 type_annotation: None,
1653 value: Some(Expr::Reference {
1654 expr: Box::new(Expr::Identifier("left".to_string(), span())),
1655 is_mutable: false,
1656 span: span(),
1657 }),
1658 ownership: OwnershipModifier::Inferred,
1659 },
1660 span(),
1661 ),
1662 Statement::Assignment(
1663 ast::Assignment {
1664 pattern: DestructurePattern::Identifier("left".to_string(), span()),
1665 value: Expr::Literal(ast::Literal::Int(3), span()),
1666 },
1667 span(),
1668 ),
1669 Statement::Expression(Expr::Identifier("shared".to_string(), span()), span()),
1670 ];
1671 let lowering = lower_function_detailed("test", &[pair_param], &body, span());
1672 assert!(!lowering.had_fallbacks);
1673 let analysis = solver::analyze(&lowering.mir, &Default::default());
1674 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1675 }
1676
1677 #[test]
1678 fn test_lowered_destructure_rest_pattern_write_while_borrowed_is_visible_to_solver() {
1679 let lowering = lower_parsed_function(
1680 r#"
1681 function test(items) {
1682 var [head, ...tail] = items
1683 let shared = &tail
1684 tail = items
1685 shared
1686 }
1687 "#,
1688 );
1689 assert!(!lowering.had_fallbacks);
1690 let analysis = solver::analyze(&lowering.mir, &Default::default());
1691 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1692 }
1693
1694 #[test]
1695 fn test_lowered_decomposition_pattern_write_while_borrowed_is_visible_to_solver() {
1696 let lowering = lower_parsed_function(
1697 r#"
1698 function test(merged) {
1699 var (left: {x}, right: {y}) = merged
1700 let shared = &left
1701 left = merged
1702 shared
1703 }
1704 "#,
1705 );
1706 assert!(!lowering.had_fallbacks);
1707 let analysis = solver::analyze(&lowering.mir, &Default::default());
1708 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1709 }
1710
1711 #[test]
1712 fn test_lowered_supported_runtime_opaque_expressions_stay_supported() {
1713 let mut overrides = std::collections::HashMap::new();
1714 overrides.insert(
1715 "digits".to_string(),
1716 Expr::Literal(ast::Literal::Int(2), span()),
1717 );
1718 let body = vec![
1719 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()),
1720 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()),
1721 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()),
1722 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()),
1723 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()),
1724 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()),
1725 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()),
1726 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()),
1727 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()),
1728 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()),
1729 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()),
1730 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()),
1731 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()),
1732 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()),
1733 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()),
1734 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()),
1735 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()),
1736 ];
1737 let lowering = lower_function_detailed("test", &[], &body, span());
1738 assert!(!lowering.had_fallbacks);
1739 }
1740
1741 #[test]
1742 fn test_lowered_assignment_expr_write_while_borrowed_is_visible_to_solver() {
1743 let body = vec![
1744 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()),
1745 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()),
1746 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()),
1747 Statement::Return(Some(Expr::Identifier("shared".to_string(), span())), span()),
1748 ];
1749 let lowering = lower_function_detailed("test", &[], &body, span());
1750 assert!(!lowering.had_fallbacks);
1751 let analysis = solver::analyze(&lowering.mir, &Default::default());
1752 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1753 }
1754
1755 #[test]
1756 fn test_lowered_property_assignment_expr_preserves_disjoint_places() {
1757 let body = vec![
1758 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()),
1759 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()),
1760 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()),
1761 ];
1762 let lowering = lower_function_detailed("test", &[], &body, span());
1763 assert!(!lowering.had_fallbacks);
1764 let analysis = solver::analyze(&lowering.mir, &Default::default());
1765 assert!(analysis.errors.is_empty());
1766 }
1767
1768 #[test]
1769 fn test_lowered_property_assignment_direct_ref_escape_is_visible_to_solver() {
1770 let lowering = lower_parsed_function(r#"
1771 function test() {
1772 var obj = { value: 0 }
1773 let x = 1
1774 obj.value = &x
1775 0
1776 }
1777 "#);
1778 assert!(!lowering.had_fallbacks);
1779 let analysis = solver::analyze(&lowering.mir, &Default::default());
1780 assert!(analysis.errors.is_empty());
1781 }
1782
1783 #[test]
1784 fn test_lowered_property_assignment_indirect_ref_escape_is_visible_to_solver() {
1785 let lowering = lower_parsed_function(r#"
1786 function test() {
1787 var obj = { value: 0 }
1788 let x = 1
1789 let r = &x
1790 obj.value = r
1791 0
1792 }
1793 "#);
1794 assert!(!lowering.had_fallbacks);
1795 let analysis = solver::analyze(&lowering.mir, &Default::default());
1796 assert!(analysis.errors.is_empty());
1797 }
1798
1799 #[test]
1800 fn test_lowered_index_assignment_direct_ref_escape_is_visible_to_solver() {
1801 let lowering = lower_parsed_function(r#"
1802 function test() {
1803 var arr = [0]
1804 let x = 1
1805 arr[0] = &x
1806 0
1807 }
1808 "#);
1809 assert!(!lowering.had_fallbacks);
1810 let analysis = solver::analyze(&lowering.mir, &Default::default());
1811 assert!(analysis.errors.is_empty());
1812 }
1813
1814 #[test]
1815 fn test_lowered_index_assignment_indirect_ref_escape_is_visible_to_solver() {
1816 let lowering = lower_parsed_function(r#"
1817 function test() {
1818 var arr = [0]
1819 let x = 1
1820 let r = &x
1821 arr[0] = r
1822 0
1823 }
1824 "#);
1825 assert!(!lowering.had_fallbacks);
1826 let analysis = solver::analyze(&lowering.mir, &Default::default());
1827 assert!(analysis.errors.is_empty());
1828 }
1829
1830 #[test]
1831 fn test_lowered_block_expr_write_while_borrowed_is_visible_to_solver() {
1832 let body = vec![
1833 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()),
1834 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()),
1835 Statement::Assignment(ast::Assignment { pattern: DestructurePattern::Identifier("x".to_string(), span()), value: Expr::Literal(ast::Literal::Int(2), span()) }, span()),
1836 Statement::Expression(Expr::Identifier("shared".to_string(), span()), span()),
1837 ];
1838 let lowering = lower_function_detailed("test", &[], &body, span());
1839 assert!(!lowering.had_fallbacks);
1840 let analysis = solver::analyze(&lowering.mir, &Default::default());
1841 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1842 }
1843
1844 #[test]
1845 fn test_lowered_let_expr_write_while_borrowed_is_visible_to_solver() {
1846 let body = vec![
1847 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()),
1848 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()),
1849 Statement::Assignment(ast::Assignment { pattern: DestructurePattern::Identifier("x".to_string(), span()), value: Expr::Literal(ast::Literal::Int(2), span()) }, span()),
1850 Statement::Expression(Expr::Identifier("shared".to_string(), span()), span()),
1851 ];
1852 let lowering = lower_function_detailed("test", &[], &body, span());
1853 assert!(!lowering.had_fallbacks);
1854 let analysis = solver::analyze(&lowering.mir, &Default::default());
1855 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
1856 }
1857
1858 #[test]
1859 fn test_lowered_if_expression_with_block_branches_stays_supported() {
1860 let block_branch = |borrow_name: &str| {
1861 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())
1862 };
1863 let body = vec![
1864 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()),
1865 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()),
1866 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()),
1867 Statement::Assignment(ast::Assignment { pattern: DestructurePattern::Identifier("x".to_string(), span()), value: Expr::Literal(ast::Literal::Int(2), span()) }, span()),
1868 ];
1869 let lowering = lower_function_detailed("test", &[], &body, span());
1870 assert!(!lowering.had_fallbacks);
1871 let analysis = solver::analyze(&lowering.mir, &Default::default());
1872 assert!(analysis.errors.is_empty());
1873 }
1874
1875 #[test]
1876 fn test_lowered_async_let_exclusive_ref_task_boundary_is_visible_to_solver() {
1877 let lowering = lower_parsed_function(r#"
1878 async function test() {
1879 let mut x = 1
1880 async let fut = &mut x
1881 }
1882 "#);
1883 assert!(!lowering.had_fallbacks);
1884 let analysis = solver::analyze(&lowering.mir, &Default::default());
1885 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ExclusiveRefAcrossTaskBoundary));
1886 }
1887
1888 #[test]
1889 fn test_lowered_async_let_nested_ref_binding_task_boundary_is_visible_to_solver() {
1890 let lowering = lower_parsed_function(r#"
1891 async function test() {
1892 let mut x = 1
1893 async let fut = {
1894 let r = &mut x
1895 r
1896 }
1897 }
1898 "#);
1899 assert!(!lowering.had_fallbacks);
1900 let analysis = solver::analyze(&lowering.mir, &Default::default());
1901 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ExclusiveRefAcrossTaskBoundary));
1902 }
1903
1904 #[test]
1905 fn test_lowered_async_let_shared_ref_task_boundary_stays_clean() {
1906 let lowering = lower_parsed_function(r#"
1907 async function test() {
1908 let x = 1
1909 async let fut = &x
1910 await fut
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::ExclusiveRefAcrossTaskBoundary));
1916 }
1917
1918 #[test]
1919 fn test_lowered_join_exclusive_ref_task_boundary_is_visible_to_solver() {
1920 let lowering = lower_parsed_function(r#"
1921 async function test() {
1922 let mut x = 1
1923 await join all {
1924 &mut x,
1925 2,
1926 }
1927 }
1928 "#);
1929 assert!(!lowering.had_fallbacks);
1930 let analysis = solver::analyze(&lowering.mir, &Default::default());
1931 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ExclusiveRefAcrossTaskBoundary));
1932 }
1933
1934 #[test]
1935 fn test_lowered_async_scope_with_async_let_stays_supported() {
1936 let lowering = lower_parsed_function(r#"
1937 async function test() {
1938 let x = 1
1939 async scope {
1940 async let fut = &x
1941 await fut
1942 }
1943 }
1944 "#);
1945 assert!(!lowering.had_fallbacks);
1946 let analysis = solver::analyze(&lowering.mir, &Default::default());
1947 assert!(analysis.errors.is_empty());
1948 }
1949
1950 #[test]
1951 fn test_lowered_closure_capture_of_reference_is_visible_to_solver() {
1952 let lowering = lower_parsed_function(r#"
1953 function test() {
1954 let x = 1
1955 let r = &x
1956 let f = || r
1957 }
1958 "#);
1959 assert!(!lowering.had_fallbacks);
1960 let analysis = solver::analyze(&lowering.mir, &Default::default());
1961 assert!(analysis.errors.is_empty());
1962 }
1963
1964 #[test]
1965 fn test_lowered_returned_array_with_ref_still_errors() {
1966 let lowering = lower_parsed_function(r#"
1967 function test() {
1968 let x = 1
1969 let arr = [&x]
1970 return arr
1971 }
1972 "#);
1973 assert!(!lowering.had_fallbacks);
1974 let analysis = solver::analyze(&lowering.mir, &Default::default());
1975 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ReferenceStoredInArray));
1976 }
1977
1978 #[test]
1979 fn test_lowered_returned_closure_with_ref_still_errors() {
1980 let lowering = lower_parsed_function(r#"
1981 function test() {
1982 let x = 1
1983 let r = &x
1984 let f = || r
1985 return f
1986 }
1987 "#);
1988 assert!(!lowering.had_fallbacks);
1989 let analysis = solver::analyze(&lowering.mir, &Default::default());
1990 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::ReferenceEscapeIntoClosure));
1991 }
1992
1993 #[test]
1994 fn test_lowered_closure_capture_of_owned_value_stays_clean() {
1995 let lowering = lower_parsed_function(r#"
1996 function test() {
1997 let x = 1
1998 let f = || x
1999 }
2000 "#);
2001 assert!(!lowering.had_fallbacks);
2002 let analysis = solver::analyze(&lowering.mir, &Default::default());
2003 assert!(analysis.errors.is_empty());
2004 }
2005
2006 #[test]
2007 fn test_lowered_list_comprehension_write_conflict_is_visible_to_solver() {
2008 let lowering = lower_parsed_function(r#"
2009 function test() {
2010 let mut x = 1
2011 let r = &x
2012 let xs = [(x = 2) for y in [1]]
2013 r
2014 }
2015 "#);
2016 assert!(!lowering.had_fallbacks);
2017 let analysis = solver::analyze(&lowering.mir, &Default::default());
2018 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
2019 }
2020
2021 #[test]
2022 fn test_lowered_from_query_write_conflict_is_visible_to_solver() {
2023 let lowering = lower_parsed_function(r#"
2024 function test() {
2025 let mut x = 1
2026 let r = &x
2027 let rows = from y in [1] where (x = 2) > 0 select y
2028 r
2029 }
2030 "#);
2031 assert!(!lowering.had_fallbacks);
2032 let analysis = solver::analyze(&lowering.mir, &Default::default());
2033 assert!(analysis.errors.iter().any(|error| error.kind == BorrowErrorKind::WriteWhileBorrowed));
2034 }
2035
2036 #[test]
2037 fn test_lowered_comptime_expr_stays_supported() {
2038 let lowering = lower_parsed_function(r#"
2039 function test() {
2040 let generated = comptime {
2041 let x = 1
2042 }
2043 }
2044 "#);
2045 assert!(!lowering.had_fallbacks);
2046 }
2047
2048 #[test]
2049 fn test_lowered_comptime_for_expr_stays_supported() {
2050 let lowering = lower_parsed_function(r#"
2051 function test() {
2052 let generated = comptime for f in [1, 2] {
2053 let y = f
2054 }
2055 }
2056 "#);
2057 assert!(!lowering.had_fallbacks);
2058 }
2059}