Skip to main content

shape_vm/mir/lowering/
mod.rs

1//! MIR lowering: AST -> MIR.
2//!
3//! Converts Shape AST function bodies into MIR basic blocks.
4//! This is the bridge between parsing and borrow analysis.
5//!
6//! ## Module structure
7//!
8//! - [`mod.rs`](self) -- Public API (`lower_function`, `lower_function_detailed`,
9//!   `compute_mutability_errors`), `MirBuilder` struct and its state machine.
10//! - [`expr`] -- Expression lowering (`lower_expr_to_temp` and its many helpers).
11//! - [`stmt`] -- Statement lowering (variable decls, assignments, control flow,
12//!   pattern destructuring).
13//! - [`helpers`] -- Shared utilities: generic container store emission, operand
14//!   collection, place projection, type inference from expressions.
15
16mod 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
65/// Builder for constructing a MIR function from AST.
66pub struct MirBuilder {
67    /// Name of the function being built.
68    name: String,
69    /// Completed basic blocks.
70    blocks: Vec<BasicBlock>,
71    /// Statements for the current (in-progress) basic block.
72    current_stmts: Vec<MirStatement>,
73    /// ID of the current basic block.
74    pub(super) current_block: BasicBlockId,
75    /// Whether the current block has already been terminated and stored.
76    current_block_finished: bool,
77    /// Next block ID to allocate.
78    next_block_id: u32,
79    /// Next local slot to allocate.
80    next_local: u16,
81    /// Dedicated return slot used by explicit `return` statements.
82    return_slot: SlotId,
83    /// Next program point.
84    next_point: u32,
85    /// Next loan ID.
86    next_loan: u32,
87    /// Local variable name -> slot mapping.
88    locals: Vec<MirLocalRecord>,
89    /// Active local name -> slot mapping for place resolution.
90    local_slots: HashMap<String, SlotId>,
91    /// Stable field indices for property-place lowering.
92    field_indices: HashMap<String, FieldIdx>,
93    /// Next field index to allocate.
94    next_field_idx: u16,
95    /// Parameter slots.
96    param_slots: Vec<SlotId>,
97    /// Per-parameter reference kind, aligned with `param_slots`.
98    param_reference_kinds: Vec<Option<BorrowKind>>,
99    /// Named-local shadowing stack for lexical scopes.
100    scope_bindings: Vec<Vec<(String, Option<SlotId>)>>,
101    /// Active loop control-flow targets.
102    loop_contexts: Vec<MirLoopContext>,
103    /// Active task-boundary capture scopes for async lowering.
104    task_boundary_capture_scopes: Vec<TaskBoundaryCaptureScope>,
105    /// Nesting depth of `async scope` blocks -- nonzero means structured concurrency.
106    pub(super) async_scope_depth: u32,
107    /// Exit block for the enclosing function.
108    exit_block: Option<BasicBlockId>,
109    /// Function span.
110    span: Span,
111    /// Spans where lowering had to fall back to placeholder/Nop handling.
112    /// Empty means clean lowering with no fallbacks.
113    fallback_spans: Vec<Span>,
114    /// Per-local container-kind track mirroring the bytecode compiler's
115    /// `mut_self_container_locals` (ADR-006 §2.7.27 / W17-mutation-writeback).
116    /// Populated at let-binding time when the initializer is a recognised
117    /// COW-container ctor (`Set()` / `HashMap()` / `Deque()` /
118    /// `PriorityQueue()`); consumed at `Expr::MethodCall` lowering to decide
119    /// whether to emit the receiver write-back assignment after the call.
120    mut_self_container_locals:
121        HashMap<SlotId, crate::compiler::mutation_writeback::ContainerKind>,
122    /// Per-slot user-struct type name for slots produced by
123    /// `Expr::StructLiteral { type_name, .. }` lowering — ADR-006 §2.7.5
124    /// producing-site classification, Phase 3 cluster-0 Round 13 T1' gap 1
125    /// closure. Threaded into `MirFunction.local_struct_type_names` at
126    /// finalization; consumed at conduit-time by the trait-method
127    /// return-kind classifier to map receiver slot → struct type name →
128    /// trait method declared return ConcreteType.
129    local_struct_type_names: HashMap<SlotId, String>,
130    /// Per-slot empty-typed-array element ConcreteType — ADR-006 §2.7.5
131    /// stamp-at-compile-time, V3-S6e-jit-specialized-vec-map-aggregate-
132    /// classify (Phase 3 cluster-0+1 Wave 3, 2026-05-16). Populated at
133    /// `lower_var_decl` for `let mut name: Array<C> = []` bindings where
134    /// `C` is a `concrete_type_from_annotation`-resolvable element type.
135    /// Threaded into `MirFunction.local_typed_array_element_types`.
136    local_typed_array_element_types: HashMap<SlotId, shape_value::v2::ConcreteType>,
137    /// Per-slot declared narrow-integer scalar `ConcreteType` — ADR-006
138    /// §2.7.5 stamp-at-compile-time, R5c-2-β-γ (c) jit-narrow-wrap.
139    /// Populated at `lower_var_decl` for `let`-bindings whose type
140    /// annotation resolves to `i8`/`i16`/`i32`/`u8`/`u16`/`u32`. Threaded
141    /// into `MirFunction.local_declared_scalar_types`.
142    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    /// Spans where lowering fell back to placeholder handling.
150    /// Used for span-granular error filtering in partial-authority mode.
151    pub fallback_spans: Vec<Span>,
152    pub binding_infos: Vec<LoweredBindingInfo>,
153    /// Reverse map from field index -> field name (inverted from `field_indices`).
154    pub field_names: HashMap<FieldIdx, String>,
155    /// All named locals (params + bindings), excluding `__mir_*` temporaries.
156    /// Used by callee summary filtering to detect local-name shadows.
157    pub all_local_names: HashSet<String>,
158}
159
160// ---------------------------------------------------------------------------
161// MirBuilder -- block and state machine management
162// ---------------------------------------------------------------------------
163
164impl 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    /// Record the user-struct type name for a slot produced by an
203    /// `Expr::StructLiteral { type_name, .. }` lowering. ADR-006 §2.7.5
204    /// producing-site classification, Phase 3 cluster-0 Round 13 T1' gap 1
205    /// closure.
206    ///
207    /// Read at conduit-time (compiler `infer_top_level_concrete_types_from_mir_with_returns`
208    /// and JIT `mir_compiler/types::infer_slot_kinds_with_concrete`) to map
209    /// the receiver slot of a trait-method dispatch call (e.g. `t.name()`
210    /// where `t = X {}`) to the struct type name `"X"`, which then resolves
211    /// the trait method declared return ConcreteType via the
212    /// `find_default_trait_impl_for_type_method` chain.
213    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    /// Record the empty-typed-array element ConcreteType for a slot
218    /// produced by `let mut name: Array<C> = []` lowering. ADR-006
219    /// §2.7.5 stamp-at-compile-time — V3-S6e-jit-specialized-vec-map-
220    /// aggregate-classify (Phase 3 cluster-0+1 Wave 3, 2026-05-16).
221    ///
222    /// Read at conduit-time
223    /// (`compiler/helpers.rs::infer_top_level_concrete_types_from_mir_with_
224    /// resolvers`) to stamp `concrete_types[slot] = Array(elem)` so the
225    /// JIT-MIR consumer's v2-fast-path (`statements.rs::v2_typed_array_
226    /// elem_kind`) activates for the empty-Aggregate site that would
227    /// otherwise short-circuit through `emit_container_store_if_needed`
228    /// (helpers.rs:128-130) without producing an `ArrayStore` statement.
229    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    /// Record the declared narrow-integer scalar `ConcreteType` for a slot
238    /// produced by a `let`-binding with an `i8`/`i16`/`i32`/`u8`/`u16`/`u32`
239    /// type annotation. ADR-006 §2.7.5 stamp-at-compile-time — R5c-2-β-γ
240    /// (c) jit-narrow-wrap.
241    ///
242    /// Read at conduit-time
243    /// (`compiler/helpers.rs::infer_top_level_concrete_types_from_mir_with_
244    /// resolvers`) to stamp `concrete_types[slot]` with the proven narrow
245    /// width, so the JIT declares the slot at the matching Cranelift width
246    /// and lowers arithmetic that wraps on overflow — matching the
247    /// bytecode VM's `AddI32`/`AddTyped` truncating opcodes.
248    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    /// Look up the declared scalar `ConcreteType` recorded for a binding
257    /// slot, if any. Used by the u64-literal kind-inference pass in
258    /// `lower_expr_to_temp`'s `Expr::BinaryOp` arm to detect a `u64`-typed
259    /// sibling operand. Returns `None` when the slot has no declared
260    /// width-carrying scalar annotation.
261    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    /// Record a recognized COW-container kind for a binding slot. Called
269    /// from let-binding lowering when the initializer is a known ctor
270    /// (`Set()` / `HashMap()` / `Deque()` / `PriorityQueue()`). Read at
271    /// `Expr::MethodCall` lowering to gate the mut-self write-back per
272    /// ADR-006 §2.7.27.
273    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    /// Look up a recognized COW-container kind for a binding slot. Returns
282    /// `None` when the binding is not a tracked container (the standard
283    /// no-writeback path).
284    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    /// Allocate a new local variable slot.
292    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    /// Allocate a local slot but defer the name->slot binding. Returns the
315    /// slot id and the name; the caller must invoke `bind_named_local` with
316    /// the returned (name, slot) pair after any pending name lookups have
317    /// resolved against the OUTER scope (e.g. for the `let x = x` shadow
318    /// pattern in `lower_var_decl` per Phase 4b Round 5c-2-α Vec.reduce
319    /// fold-state JIT divergence fix 2026-05-19).
320    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    /// Public: bind a name to a previously-allocated slot. Used after
363    /// `alloc_local_with_binding_deferred` to register the name once the
364    /// initializer expression has been lowered against the OUTER scope.
365    pub(super) fn bind_named_local_pub(&mut self, name: String, slot: SlotId) {
366        self.bind_named_local(name, slot);
367    }
368
369    /// Allocate a temporary local slot that should not participate in name resolution.
370    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    /// Register a parameter slot.
376    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    /// Look up the current slot for a named local.
390    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    /// Get or allocate a stable field index for a property name.
403    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        // Legacy: called without a span. Use the current function span as fallback.
456        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    /// Allocate a new program point.
531    pub fn next_point(&mut self) -> Point {
532        let p = Point(self.next_point);
533        self.next_point += 1;
534        p
535    }
536
537    /// Allocate a new loan ID.
538    pub fn next_loan(&mut self) -> LoanId {
539        let l = LoanId(self.next_loan);
540        self.next_loan += 1;
541        l
542    }
543
544    /// Create a new basic block and return its ID.
545    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    /// Push a statement into the current block.
552    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    /// Finish the current block with a terminator and switch to a new block.
567    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    /// Start building a new block (after finishing the previous one).
581    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    /// Emit a function call as a block terminator. Finishes current block
588    /// with TerminatorKind::Call and starts a continuation block.
589    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    /// Finalize and produce the MIR function.
610    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        // Sort blocks by ID so that MirFunction::block(id) can index by id.0
627        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
662// ---------------------------------------------------------------------------
663// Public API
664// ---------------------------------------------------------------------------
665
666pub(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
679/// Lower a function body (list of statements) into MIR.
680pub 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    // Register parameters
689    for param in params {
690        let type_info = if param.is_reference {
691            LocalTypeInfo::NonCopy // references are always tracked
692        } else {
693            LocalTypeInfo::Unknown // will be resolved during analysis
694        };
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            // cluster-2-closure-wave-1-iter-statemachine (2026-05-16):
717            // seed `local_typed_array_element_types` from a typed-array
718            // param annotation (`self: Vec<C>` / `xs: Array<C>` / etc.).
719            // The conduit producer at `crates/shape-vm/src/compiler/
720            // helpers.rs::infer_top_level_concrete_types_from_mir_with_
721            // resolvers` stamps `concrete_types[slot] = Array(elem)` for
722            // the param from this map (reusing the V3-S6e empty-array
723            // stamping pass — `concrete_type_from_annotation` resolves
724            // `Vec<C>` and `Array<C>` annotations).
725            //
726            // Load-bearing for the index-counter iter state-machine
727            // emitted by `lower_for_expr` / `lower_for_loop`
728            // generic-iterator branches at this checkpoint: when the
729            // iter is a typed-array receiver (e.g. `for item in self`
730            // inside the post-monomorphization specialized `Vec.map<T,
731            // U>` body where `self: Vec<T>` substitutes to `self:
732            // Array<I64>`), the slot-move propagation pass in the
733            // conduit producer flows `Array(I64)` from the param slot to
734            // the iter_slot, and the JIT-MIR `v2_typed_array_elem_kind`
735            // fast path fires for the `len()` Call terminator and the
736            // `Place::Index` per-iteration read. Without this seed, the
737            // trampoline gets receiver_kind=UInt64 (the §2.7.5 carrier
738            // fallback) and the `len` dispatch returns 0 (the loop body
739            // executes zero times).
740            //
741            // ADR-006 §2.7.5 stamp-at-compile-time: the param annotation
742            // IS the proof of the receiver's ConcreteType at compile
743            // time. No tag-bit decode, no Bool-default — params without
744            // typed-array annotations leave no entry in the map and the
745            // conduit producer leaves `concrete_types[slot]` as `Void`
746            // per §2.7.5.1.
747            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                &param.pattern,
764                &Place::Local(slot),
765                param.span(),
766                binding_metadata,
767            );
768        }
769    }
770
771    // Create the exit block
772    let exit_block = builder.new_block();
773    builder.set_exit_block(exit_block);
774
775    // Lower body statements
776    stmt::lower_statements(&mut builder, body, exit_block);
777
778    // If current block hasn't been finished (no explicit return), emit goto exit
779    if !builder.current_block_finished {
780        builder.finish_block(TerminatorKind::Goto(exit_block), span);
781    }
782
783    // Create exit block with Return terminator
784    builder.start_block(exit_block);
785    builder.finish_block(TerminatorKind::Return, span);
786
787    builder.build()
788}
789
790/// Lower a function body (list of statements) into MIR.
791pub 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// ---------------------------------------------------------------------------
837// Tests
838// ---------------------------------------------------------------------------
839
840#[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); // entry + exit
867        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); // at least x + temp
885        // Should have at least 2 blocks (entry + exit)
886        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 x = 1; let y = x; (x live after first stmt, dead after second)
992        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        // The MIR lowers and liveness computes without panic
1034    }
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    /// W15.2-LANG-5 regression. `Pattern::Typed` arms in a `match`
1768    /// expression must lower to a real discriminator — pre-fix
1769    /// `lower_match_pattern_condition_operand` grouped `Pattern::Typed`
1770    /// with `Identifier`/`Wildcard` (returning `None` for the condition
1771    /// operand), so the MIR builder emitted a kind-blind
1772    /// `TerminatorKind::Goto(body_block)` for every typed arm. Under JIT
1773    /// the first arm always won (e.g. `match x: int | string { n: int =>
1774    /// 100, s: string => 200 }` returned 100 for both `7` and `"hi"`).
1775    ///
1776    /// Post-fix the lowering emits `Rvalue::TypePatternTest` per arm with
1777    /// the producer-side stamped `TypeAnnotation`, then a `SwitchBool` on
1778    /// the resulting Bool slot. This test pins the producer-side
1779    /// classification by scanning the lowered MIR for the new Rvalue.
1780    #[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    /// W15.2-LANG-1 (Phase 4b, 2026-05-18) regression test. Pre-fix the
1814    /// MIR lowering of `Pattern::Constructor` for non-trinity (user-defined)
1815    /// enum variants returned `Some(Operand::Copy(Place::Local(scrutinee)))`
1816    /// — the raw `Arc<TypedObjectStorage>` pointer bits — as the SwitchBool
1817    /// condition. The JIT consumer's generic I64-truthy path then evaluated
1818    /// the non-zero pointer non-deterministically (silently empty output for
1819    /// `match Color::Red { Color::Red => print("red"), Color::Green => ...
1820    /// , Color::Blue => ... }`, book snippet `enums.mdx:113`).
1821    ///
1822    /// Post-fix the lowering emits `Rvalue::EnumDiscriminantTest` per arm
1823    /// with the producer-side stamped (enum_name, variant_name) pair, then
1824    /// a `SwitchBool` on the resulting Bool slot. This test pins the
1825    /// producer-side classification by scanning the lowered MIR for the
1826    /// new Rvalue.
1827    #[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        // Verify each carries the expected (enum_name, variant_name) pair
1861        // verbatim per ADR-006 §2.7.5 stamp-at-compile-time.
1862        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        // This test verifies that numeric literals are lowered to MirConstant
2378        // with their actual values, not placeholder MirConstant::Int(0).
2379        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        // Collect all constants from the MIR.
2417        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        // Verify: Int(42) is present, not Int(0)
2427        assert!(
2428            constants.contains(&MirConstant::Int(42)),
2429            "expected MirConstant::Int(42) in {constants:?}"
2430        );
2431        // Verify: Float(2.5 bits) is present, not Float(0)
2432        assert!(
2433            constants.contains(&MirConstant::Float(f64::to_bits(2.5))),
2434            "expected MirConstant::Float(2.5) in {constants:?}"
2435        );
2436        // Verify: Bool(true) is present
2437        assert!(
2438            constants.contains(&MirConstant::Bool(true)),
2439            "expected MirConstant::Bool(true) in {constants:?}"
2440        );
2441    }
2442
2443    /// W15.2-LANG-2 regression: `continue` inside a `for i in 0..N { ... }`
2444    /// body must NOT jump directly to the loop header — it must route
2445    /// through a dedicated `continue_target` block that performs the
2446    /// counter increment, otherwise `continue` at idx=K leaves idx=K
2447    /// and re-enters the body infinitely. Verified by structural MIR
2448    /// inspection: the loop-context's `continue_block` exposed via the
2449    /// body's `Continue` statement must NOT equal the block targeted by
2450    /// the header's `SwitchBool { true_bb, .. }`. Empirically this same
2451    /// invariant repaired the audit reproducer (`fundamentals/control-
2452    /// flow.mdx:87` snippet `A__fundamentals__control-flow__07__L0087`)
2453    /// from JIT `0\n1` (infinite-loop after first continue) to JIT
2454    /// `0\n1\n3\n4\n5` matching VM.
2455    #[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        // Find the header block: it has a SwitchBool terminator whose
2463        // condition is computed by a BinaryOp::Lt comparison (the
2464        // `counter < end` check). There is exactly one such block in
2465        // this fixture.
2466        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        // Find a block whose terminator is `Goto(target)` AND whose
2487        // statements contain the counter advance (`x = x + 1`). The
2488        // ForIn lowering may produce more than one block matching one
2489        // half of this pattern, so we require BOTH halves to identify
2490        // the `continue_target` block unambiguously.
2491        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        // The fix's invariant: the `continue` statement inside the body
2511        // must terminate at a block (the dedicated `continue_target`)
2512        // that is DIFFERENT from the body block (the header's true_bb)
2513        // — that distinction is exactly what makes the increment run
2514        // for the `continue` path.
2515        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        // Sanity: the increment block must NOT equal the header itself
2522        // (otherwise the increment runs at every iteration BEFORE the
2523        // cond check, which is a different broken shape).
2524        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    // Phase 4b Round 5c-2-α Vec.reduce fold-state JIT divergence
2532    // (v0.3-gating SOUNDNESS BUG) regression tests per supervisor ratify
2533    // 2026-05-19. Sister-class to LANG-9-spin-3-first.
2534    //
2535    // Root cause: MIR `lower_var_decl` allocated the new binding slot AND
2536    // bound the name (`bind_named_local`) BEFORE lowering the initializer
2537    // expression. For the same-name shadow pattern `let acc = acc`, the
2538    // RHS identifier read resolved through `lookup_local("acc")` against
2539    // the JUST-bound new slot (uninitialized!) instead of the OUTER one.
2540    //
2541    // Fix shape: `lower_var_decl` now allocates via
2542    // `alloc_local_with_binding_deferred` (slot record created, name
2543    // resolution deferred), lowers the initializer (RHS reads OUTER
2544    // binding for the name), then registers the name via
2545    // `bind_named_local_pub` so the new shadow becomes visible to
2546    // subsequent statements.
2547    //
2548    // Surface: Vec.reduce stdlib body after Phase C closure inlining
2549    // becomes `acc = { let acc = acc; let x = item; acc + x }` (per
2550    // `compiler/monomorphization/substitution.rs::build_inlined_closure
2551    // _block`). The same-name `let acc = acc` shadow tripped the MIR-side
2552    // gap, returning the last item value instead of the folded sum.
2553    // Original empirical reproducer (W15.2-D close): `[1,2,3,4].reduce(
2554    // |a,b| a+b, 0)` VM=10 / JIT=4.
2555
2556    #[test]
2557    fn test_shadow_with_same_name_lowers_init_against_outer_binding() {
2558        // `let x = 1; let x = x; x` — the inner `let x = x` must read
2559        // the OUTER x's value (1), not the inner uninitialized slot.
2560        // Pre-fix: MIR emitted `Assign(inner_x, Use(Copy(inner_x)))` —
2561        // self-referential, reading the new slot's default (0) instead
2562        // of the outer slot's 1.
2563        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        // Find the second VariableDecl's Assign — the RHS Operand must
2589        // reference a DIFFERENT slot than the LHS destination (the OUTER
2590        // x's slot, not the INNER x's slot).
2591        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        // The second `let x = x` shadow must have dst != src (outer
2608        // slot read into inner slot). Pre-fix this would be dst == src.
2609        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 mut acc = 0; for item in [1,2,3] { acc = { let acc = acc;
2624        // acc + item } }` — mirrors the Vec.reduce body shape after
2625        // Phase C closure inlining. The block-result `Assign` must
2626        // write the per-iteration sum back to OUTER acc.
2627        //
2628        // Empirical assertion: the for-loop body's last statement is
2629        // `Assign(outer_acc, ...)` where the RHS Use references a slot
2630        // populated by `acc + item` — NOT a self-Assign of inner_acc to
2631        // itself.
2632        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        // Sanity: the function lowered without panic.
2647        assert!(mir.num_locals >= 1);
2648    }
2649
2650    #[test]
2651    fn test_let_x_x_does_not_self_reference_in_mir() {
2652        // Negative test: lower `let x = 1; let x = x;` and confirm the
2653        // resulting MIR's second Assign reads a DIFFERENT slot than it
2654        // writes. Pre-fix produced `Assign(SlotId(n), Use(Copy(SlotId(n))))`.
2655        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        // Walk all Assigns; ensure NONE have the pattern
2681        // `Assign(Local(n), Use(Copy|Move(Local(n))))` (self-Use).
2682        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        // Sanity: `let x = 1; let y = x;` works the same way after the
2707        // fix as before — the second decl's slot is DIFFERENT from
2708        // x's, and the RHS reads x's slot. This isn't a shadow, but
2709        // exercises the deferred-bind path uniformly.
2710        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        // y's slot must differ from x's slot, and the RHS of y's Assign
2736        // reads x's slot.
2737        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        // The slot-to-slot Use Assign (representing `let y = x`) must
2753        // exist with dst != src.
2754        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}