Repository: microsoft/qsharp (rust)

Issue:
DumpOperation doesn't need its argument to have an adjoint
DumpOperation uses only `op`, not its adjoint, so it should work fine with operations that do not have an adjoint. I ran into a case when I need to print the matrix of a unitary operation that is implemented using mutable variables (so not automatically or easily adjointable) but includes a sequence of gates that has a matrix representation. And, looking at the SingleQ example in https://learn.microsoft.com/en-us/azure/quantum/testing-debugging, dump_operation doesn't have the same restriction.

Pull request:
DumpOperation doesn't need its argument to have an adjoint
Fixes #2341

Patch:
diff --git a/compiler/qsc_linter/src/lint_groups.rs b/compiler/qsc_linter/src/lint_groups.rs
index 104bc37b..5ece1022 100644
--- a/compiler/qsc_linter/src/lint_groups.rs
+++ b/compiler/qsc_linter/src/lint_groups.rs
@@ -29,6 +29,8 @@ impl LintGroup {
                 vec![
                     LintKind::Ast(DeprecatedNewtype),
                     LintKind::Ast(DeprecatedSet),
+                    LintKind::Ast(DeprecatedAssignUpdateExpr),
+                    LintKind::Ast(DeprecatedUpdateExpr),
                     LintKind::Hir(DeprecatedFunctionConstructor),
                     LintKind::Hir(DeprecatedWithOperator),
                     LintKind::Hir(DeprecatedDoubleColonOperator),
diff --git a/compiler/qsc_linter/src/lints/ast.rs b/compiler/qsc_linter/src/lints/ast.rs
index 72bdf16a..4a223ce6 100644
--- a/compiler/qsc_linter/src/lints/ast.rs
+++ b/compiler/qsc_linter/src/lints/ast.rs
@@ -3,8 +3,11 @@
 
 use super::lint;
 use crate::linter::{ast::declare_ast_lints, Compilation};
-use qsc_ast::ast::{BinOp, Block, Expr, ExprKind, Item, ItemKind, Lit, NodeId, Stmt, StmtKind};
+use qsc_ast::ast::{
+    BinOp, Block, Expr, ExprKind, Item, ItemKind, Lit, NodeId, Stmt, StmtKind, TernOp,
+};
 use qsc_data_structures::span::Span;
+use qsc_hir::ty::Ty;
 
 // Read Me:
 //  To add a new lint add a new tuple to this structure. The tuple has four elements:
@@ -30,6 +33,8 @@ declare_ast_lints! {
     (DeprecatedNewtype, LintLevel::Allow, "deprecated `newtype` declarations", "`newtype` declarations are deprecated, use `struct` instead"),
     (DeprecatedSet, LintLevel::Allow, "deprecated use of `set` keyword", "the `set` keyword is deprecated for assignments and can be removed"),
     (DiscourageChainAssignment, LintLevel::Warn, "discouraged use of chain assignment", "assignment expressions always return `Unit`, so chaining them may not be useful"),
+    (DeprecatedAssignUpdateExpr, LintLevel::Allow, "deprecated use of update assignment expressions", "update assignment expressions \"a w/= b <- c\" are deprecated; consider using explicit assignment instead \"a[b] = c\""),
+    (DeprecatedUpdateExpr, LintLevel::Allow, "deprecated use of update expressions", "update expressions \"a w/ b <- c\" are deprecated; consider using explicit assignment instead"),
 }
 
 #[derive(Default)]
@@ -259,3 +264,40 @@ impl AstLintPass for DiscourageChainAssignment {
         }
     }
 }
+
+#[derive(Default)]
+struct DeprecatedAssignUpdateExpr {
+    level: LintLevel,
+}
+
+impl AstLintPass for DeprecatedAssignUpdateExpr {
+    fn check_expr(&mut self, expr: &Expr, buffer: &mut Vec<Lint>, compilation: Compilation) {
+        if let ExprKind::AssignUpdate(record, index, value) = expr.kind.as_ref() {
+            if let Some(Ty::Array(_)) = compilation.compile_unit.ast.tys.terms.get(record.id) {
+                let record_src = compilation.get_source_code(record.span);
+                let index_src = compilation.get_source_code(index.span);
+                let value_src = compilation.get_source_code(value.span);
+                let edit = vec![(
+                    format!("{record_src}[{index_src}] = {value_src}"),
+                    expr.span,
+                )];
+                buffer.push(lint!(self, expr.span, edit));
+            }
+        }
+    }
+}
+
+#[derive(Default)]
+struct DeprecatedUpdateExpr {
+    level: LintLevel,
+}
+
+impl AstLintPass for DeprecatedUpdateExpr {
+    fn check_expr(&mut self, expr: &Expr, buffer: &mut Vec<Lint>, compilation: Compilation) {
+        if let ExprKind::TernOp(TernOp::Update, record, ..) = expr.kind.as_ref() {
+            if let Some(Ty::Array(_)) = compilation.compile_unit.ast.tys.terms.get(record.id) {
+                buffer.push(lint!(self, expr.span));
+            }
+        }
+    }
+}
diff --git a/compiler/qsc_passes/src/common.rs b/compiler/qsc_passes/src/common.rs
index 3fc406bc..f36bc21a 100644
--- a/compiler/qsc_passes/src/common.rs
+++ b/compiler/qsc_passes/src/common.rs
@@ -17,6 +17,16 @@ pub(crate) fn generated_name(name: &str) -> Rc<str> {
     Rc::from(format!("@{name}"))
 }
 
+pub(crate) fn gen_ident(assigner: &mut Assigner, label: &str, ty: Ty, span: Span) -> IdentTemplate {
+    let id = assigner.next_node();
+    IdentTemplate {
+        id,
+        span,
+        ty,
+        name: generated_name(&format!("{label}_{id}")),
+    }
+}
+
 #[derive(Debug, Clone)]
 pub(crate) struct IdentTemplate {
     pub id: NodeId,
diff --git a/compiler/qsc_passes/src/index_assignment.rs b/compiler/qsc_passes/src/index_assignment.rs
new file mode 100644
index 00000000..640b1d18
--- /dev/null
+++ b/compiler/qsc_passes/src/index_assignment.rs
@@ -0,0 +1,309 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#[cfg(test)]
+mod tests;
+
+use std::mem::take;
+
+use qsc_data_structures::span::Span;
+use qsc_hir::{
+    assigner::Assigner,
+    hir::{BinOp, Block, Expr, ExprKind, Mutability, Stmt, StmtKind},
+    mut_visit::{walk_expr, MutVisitor},
+    ty::{Prim, Ty},
+};
+
+use crate::common::{gen_ident, IdentTemplate};
+
+pub(crate) struct ConvertToWSlash<'a> {
+    pub(crate) assigner: &'a mut Assigner,
+}
+
+impl ConvertToWSlash<'_> {
+    /// Creates local binding statements for each index expression and collects identifier templates.
+    ///
+    /// # Parameters
+    /// - `index_exprs`: A vector of mutable references to boxed index expressions (from LHS of assignment).
+    ///
+    /// # Returns
+    /// A tuple containing:
+    /// - A vector of statements binding each index to a local identifier.
+    /// - A vector of identifier templates for each index.
+    fn create_index_bindings(
+        &mut self,
+        index_exprs: Vec<&mut Box<Expr>>,
+    ) -> (Vec<Stmt>, Vec<IdentTemplate>) {
+        let mut stmts = vec![];
+        let mut ids = vec![];
+        for index in index_exprs.into_iter().rev() {
+            let id = gen_ident(self.assigner, "index", index.ty.clone(), index.span);
+
+            // Create binding statement
+            let stmt = id.gen_id_init(Mutability::Immutable, *take(index), self.assigner);
+
+            stmts.push(stmt);
+            ids.push(id);
+        }
+
+        (stmts, ids)
+    }
+
+    /// Creates a target expression for either the outermost `w/=` or an inner `w/` operation.
+    ///
+    /// # Parameters
+    /// - `nested_level`: The index of the current nesting level (0 is outermost).
+    /// - `index_ids`: Slice of identifier templates for all indices.
+    /// - `base_array`: The base array expression being updated.
+    ///
+    /// # Returns
+    /// An `Expr` representing the target array or subarray for the assignment.
+    fn create_target_expr(
+        &mut self,
+        nested_level: usize,
+        index_ids: &[IdentTemplate],
+        base_array: &Expr,
+    ) -> Expr {
+        if nested_level == 0 {
+            // For the outermost `w/=`, use the base array reference
+            let mut array_expr = base_array.clone();
+            array_expr.id = self.assigner.next_node();
+            array_expr
+        } else {
+            // For inner `w/`, use the array indexing
+            self.create_indexed_expr(nested_level, index_ids, base_array)
+        }
+    }
+
+    /// Creates an indexed expression for inner `w/` operations.
+    ///
+    /// # Parameters
+    /// - `nested_level`: The index of the current nesting level (0 is outermost).
+    /// - `index_ids`: Slice of identifier templates for all indices.
+    /// - `base_array`: The base array expression being indexed.
+    ///
+    /// # Returns
+    /// An `Expr` representing the nested indexed array expression.
+    fn create_indexed_expr(
+        &mut self,
+        nested_level: usize,
+        index_ids: &[IdentTemplate],
+        base_array: &Expr,
+    ) -> Expr {
+        let mut array_expr = base_array.clone();
+        array_expr.id = self.assigner.next_node();
+        let mut index_type = array_expr.ty.clone();
+
+        for target_index in &index_ids[..nested_level] {
+            // Update the array type based on the index type
+            index_type = calculate_indexed_type(index_type, target_index);
+
+            array_expr = Expr {
+                id: self.assigner.next_node(),
+                span: Span::default(),
+                ty: index_type.clone(),
+                kind: ExprKind::Index(
+                    Box::new(array_expr),
+                    Box::new(target_index.gen_local_ref(self.assigner)),
+                ),
+            };
+        }
+
+        array_expr
+    }
+
+    /// Creates a `w/` or `w/=` expression.
+    ///
+    /// # Parameters
+    /// - `is_w_slash_eq`: Whether this is the outermost `w/=` (true) or an inner `w/` (false).
+    /// - `target_expr`: The array or subarray being updated.
+    /// - `index_id`: The identifier template for the current index.
+    /// - `value_expr`: The expression to assign.
+    /// - `expr_ty`: The type of the overall expression.
+    ///
+    /// # Returns
+    /// An `Expr` representing the update or assignment.
+    fn create_update_expr(
+        &mut self,
+        is_w_slash_eq: bool,
+        target_expr: Expr,
+        index_id: &IdentTemplate,
+        value_expr: Expr,
+        expr_ty: &Ty,
+    ) -> Expr {
+        Expr {
+            id: self.assigner.next_node(),
+            span: Span::default(),
+            ty: if is_w_slash_eq {
+                expr_ty.clone()
+            } else {
+                target_expr.ty.clone()
+            },
+            kind: if is_w_slash_eq {
+                // Outermost `w/=`
+                ExprKind::AssignIndex(
+                    Box::new(target_expr),
+                    Box::new(index_id.gen_local_ref(self.assigner)),
+                    Box::new(value_expr),
+                )
+            } else {
+                // Inner `w/`
+                ExprKind::UpdateIndex(
+                    Box::new(target_expr),
+                    Box::new(index_id.gen_local_ref(self.assigner)),
+                    Box::new(value_expr),
+                )
+            },
+        }
+    }
+
+    /// Creates a block expression containing index bindings and the final `w/` expression.
+    ///
+    /// # Parameters
+    /// - `stmts`: Statements binding each index to a local identifier.
+    /// - `assign_expr`: The final `w/=` expression.
+    /// - `span`: The span for the block expression.
+    /// - `ty`: The type of the block expression.
+    ///
+    /// # Returns
+    /// An `Expr` representing the block with all bindings and the update.
+    fn create_block_expr(
+        &mut self,
+        stmts: Vec<Stmt>,
+        assign_expr: Expr,
+        span: Span,
+        ty: &Ty,
+    ) -> Expr {
+        // Add the final `w/=` expression as a statement
+        let mut all_stmts = stmts;
+        all_stmts.push(Stmt {
+            id: self.assigner.next_node(),
+            span: Span::default(),
+            kind: StmtKind::Expr(assign_expr),
+        });
+
+        // Wrap all statements in a block expression
+        Expr {
+            id: self.assigner.next_node(),
+            span,
+            ty: ty.clone(),
+            kind: ExprKind::Block(Block {
+                id: self.assigner.next_node(),
+                span,
+                ty: ty.clone(),
+                stmts: all_stmts,
+            }),
+        }
+    }
+}
+
+impl MutVisitor for ConvertToWSlash<'_> {
+    #[allow(clippy::too_many_lines)]
+    fn visit_expr(&mut self, expr: &mut Expr) {
+        walk_expr(self, expr);
+
+        if let Some((op, lhs, rhs)) = is_assign_or_assign_op(&mut expr.kind) {
+            let mut index_exprs = vec![];
+            let mut current = lhs;
+            let mut curr_kind = &mut current.kind;
+
+            // Traverse the LHS and collect mutable references to index expressions
+            while let ExprKind::Index(array, index) = curr_kind {
+                index_exprs.push(index);
+                current = array;
+                curr_kind = &mut current.kind;
+            }
+
+            // If the LHS is not an array index, return early
+            if index_exprs.is_empty() || !matches!(curr_kind, ExprKind::Var(_, _)) {
+                return;
+            }
+
+            // Can't `.clone()` current here, so make a clone manually
+            let base_array = Box::new(Expr {
+                id: current.id,
+                span: current.span,
+                ty: current.ty.clone(),
+                kind: curr_kind.clone(),
+            });
+
+            // Create local binding statements for each index and collect index templates
+            let (stmts, index_ids) = self.create_index_bindings(index_exprs);
+
+            // Build the "a <op> b" part
+            if let Some(op) = op {
+                let target_expr = self.create_target_expr(index_ids.len(), &index_ids, &base_array);
+                let bin_expr = Expr {
+                    id: self.assigner.next_node(),
+                    span: expr.span,
+                    ty: expr.ty.clone(),
+                    kind: ExprKind::BinOp(*op, Box::new(target_expr), take(rhs)),
+                };
+                *rhs = Box::new(bin_expr);
+            }
+
+            // Construct the nested `w/` expressions
+            let mut update_expr = *take(rhs); // start with the ultimate value and expand out from there
+            for (i, id) in index_ids.iter().enumerate().rev() {
+                let target_expr = self.create_target_expr(i, &index_ids, &base_array);
+                update_expr =
+                    self.create_update_expr(i == 0, target_expr, id, update_expr, &expr.ty);
+            }
+
+            // Create and replace with the block expression
+            *expr = self.create_block_expr(stmts, update_expr, expr.span, &expr.ty);
+        }
+    }
+}
+
+/// Calculates the type resulting from indexing into an array.
+///
+/// # Parameters
+/// - `index_type`: The type of the array being indexed.
+/// - `target_index`: The identifier template for the index expression.
+///
+/// # Returns
+/// The resulting type after applying the index. Returns `Ty::Err` if the `index_type` is
+/// not an array type or `target_index` is not an integer or range type.
+fn calculate_indexed_type(index_type: Ty, target_index: &IdentTemplate) -> Ty {
+    if matches!(index_type, Ty::Array(_)) {
+        // Check the target_index's type
+        match &target_index.ty {
+            // If indexing with Range, preserve the array type
+            Ty::Prim(Prim::Range) => index_type,
+            // For Int, extract the element type
+            Ty::Prim(Prim::Int) => {
+                if let Ty::Array(element_ty) = index_type {
+                    *element_ty
+                } else {
+                    // If the type is not an array, return an error
+                    unreachable!("Already checked for array type above");
+                }
+            }
+            // For any other type, return an error
+            _ => Ty::Err,
+        }
+    } else {
+        Ty::Err
+    }
+}
+
+/// Checks if the given expression kind is an assignment or compound assignment,
+/// returning the operator (if any), the left-hand side, and the right-hand side as mutable references.
+///
+/// # Parameters
+/// - `expr_kind`: The expression kind to check.
+///
+/// # Returns
+/// - `Some((op, lhs, rhs))` if `expr_kind` is an assignment (`Assign`) or compound assignment (`AssignOp`),
+///   where `op` is `None` for `Assign` and `Some(BinOp)` for `AssignOp`.
+/// - `None` if `expr_kind` is not an assignment or compound assignment.
+fn is_assign_or_assign_op(
+    expr_kind: &mut ExprKind,
+) -> Option<(Option<&mut BinOp>, &mut Box<Expr>, &mut Box<Expr>)> {
+    match expr_kind {
+        ExprKind::Assign(lhs, rhs) => Some((None, lhs, rhs)),
+        ExprKind::AssignOp(op, lhs, rhs) => Some((Some(op), lhs, rhs)),
+        _ => None,
+    }
+}
diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs
index 72838140..a23f4295 100644
--- a/compiler/qsc_passes/src/lib.rs
+++ b/compiler/qsc_passes/src/lib.rs
@@ -8,6 +8,7 @@ mod common;
 mod conjugate_invert;
 mod entry_point;
 mod id_update;
+mod index_assignment;
 mod invert_block;
 mod logic_sep;
 mod loop_unification;
@@ -20,6 +21,7 @@ mod test_attribute;
 use callable_limits::CallableLimits;
 use capabilitiesck::{check_supported_capabilities, lower_store, run_rca_pass};
 use entry_point::generate_entry_expr;
+use index_assignment::ConvertToWSlash;
 use loop_unification::LoopUni;
 use miette::Diagnostic;
 use qsc_data_structures::target::TargetCapabilityFlags;
@@ -102,6 +104,9 @@ impl PassContext {
         call_limits.visit_package(package);
         let callable_errors = call_limits.errors;
 
+        ConvertToWSlash { assigner }.visit_package(package);
+        Validator::default().visit_package(package);
+
         self.borrow_check.visit_package(package);
         let borrow_errors = &mut self.borrow_check.errors;
 
diff --git a/compiler/qsc_passes/src/loop_unification.rs b/compiler/qsc_passes/src/loop_unification.rs
index 21c97856..4c752e48 100644
--- a/compiler/qsc_passes/src/loop_unification.rs
+++ b/compiler/qsc_passes/src/loop_unification.rs
@@ -12,7 +12,7 @@ use qsc_hir::{
     ty::{GenericArg, Prim, Ty},
 };
 
-use crate::common::{create_gen_core_ref, generated_name, IdentTemplate};
+use crate::common::{create_gen_core_ref, gen_ident, IdentTemplate};
 use crate::CORE_NAMESPACE;
 
 #[cfg(test)]
@@ -32,7 +32,12 @@ impl LoopUni<'_> {
         span: Span,
     ) -> Expr {
         let cond_span = cond.span;
-        let continue_cond_id = self.gen_ident("continue_cond", Ty::Prim(Prim::Bool), cond_span);
+        let continue_cond_id = gen_ident(
+            self.assigner,
+            "continue_cond",
+            Ty::Prim(Prim::Bool),
+            cond_span,
+        );
         let continue_cond_init = continue_cond_id.gen_id_init(
             Mutability::Mutable,
             Expr {
@@ -127,7 +132,12 @@ impl LoopUni<'_> {
     ) -> Expr {
         let iterable_span = iterable.span;
 
-        let array_id = self.gen_ident("array_id", iterable.ty.clone(), iterable_span);
+        let array_id = gen_ident(
+            self.assigner,
+            "array_id",
+            iterable.ty.clone(),
+            iterable_span,
+        );
         let array_capture = array_id.gen_id_init(Mutability::Immutable, *iterable, self.assigner);
 
         let Ty::Array(item_ty) = &array_id.ty else {
@@ -145,7 +155,7 @@ impl LoopUni<'_> {
             array_id.span,
         );
         len_callee.id = self.assigner.next_node();
-        let len_id = self.gen_ident("len_id", Ty::Prim(Prim::Int), iterable_span);
+        let len_id = gen_ident(self.assigner, "len_id", Ty::Prim(Prim::Int), iterable_span);
         let len_capture = len_id.gen_id_init(
             Mutability::Immutable,
             Expr {
@@ -160,7 +170,12 @@ impl LoopUni<'_> {
             self.assigner,
         );
 
-        let index_id = self.gen_ident("index_id", Ty::Prim(Prim::Int), iterable_span);
+        let index_id = gen_ident(
+            self.assigner,
+            "index_id",
+            Ty::Prim(Prim::Int),
+            iterable_span,
+        );
         let index_init = index_id.gen_steppable_id_init(
             Mutability::Mutable,
             Expr {
@@ -246,24 +261,34 @@ impl LoopUni<'_> {
     ) -> Expr {
         let iterable_span = iterable.span;
 
-        let range_id = self.gen_ident("range_id", Ty::Prim(Prim::Range), iterable_span);
+        let range_id = gen_ident(
+            self.assigner,
+            "range_id",
+            Ty::Prim(Prim::Range),
+            iterable_span,
+        );
         let range_capture = range_id.gen_id_init(Mutability::Immutable, *iterable, self.assigner);
 
-        let index_id = self.gen_ident("index_id", Ty::Prim(Prim::Int), iterable_span);
+        let index_id = gen_ident(
+            self.assigner,
+            "index_id",
+            Ty::Prim(Prim::Int),
+            iterable_span,
+        );
         let index_init = index_id.gen_steppable_id_init(
             Mutability::Mutable,
             range_id.gen_field_access(PrimField::Start, self.assigner),
             self.assigner,
         );
 
-        let step_id = self.gen_ident("step_id", Ty::Prim(Prim::Int), iterable_span);
+        let step_id = gen_ident(self.assigner, "step_id", Ty::Prim(Prim::Int), iterable_span);
         let step_init = step_id.gen_id_init(
             Mutability::Immutable,
             range_id.gen_field_access(PrimField::Step, self.assigner),
             self.assigner,
         );
 
-        let end_id = self.gen_ident("end_id", Ty::Prim(Prim::Int), iterable_span);
+        let end_id = gen_ident(self.assigner, "end_id", Ty::Prim(Prim::Int), iterable_span);
         let end_init = end_id.gen_id_init(
             Mutability::Immutable,
             range_id.gen_field_access(PrimField::End, self.assigner),
@@ -311,16 +336,6 @@ impl LoopUni<'_> {
             }),
         }
     }
-
-    fn gen_ident(&mut self, label: &str, ty: Ty, span: Span) -> IdentTemplate {
-        let id = self.assigner.next_node();
-        IdentTemplate {
-            id,
-            span,
-            ty,
-            name: generated_name(&format!("{label}_{id}")),
-        }
-    }
 }
 
 impl MutVisitor for LoopUni<'_> {
diff --git a/library/std/src/Std/Diagnostics.qs b/library/std/src/Std/Diagnostics.qs
index 6e17ef42..eded8858 100644
--- a/library/std/src/Std/Diagnostics.qs
+++ b/library/std/src/Std/Diagnostics.qs
@@ -90,7 +90,7 @@ function DumpRegister(register : Qubit[]) : Unit {
 /// the matrix displayed may reflect any global phase that has accumulated from operations
 /// on those other qubits.
 @SimulatableIntrinsic()
-operation DumpOperation(nQubits : Int, op : Qubit[] => Unit is Adj) : Unit {
+operation DumpOperation(nQubits : Int, op : Qubit[] => Unit) : Unit {
     use (targets, extra) = (Qubit[nQubits], Qubit[nQubits]);
     for i in 0..nQubits - 1 {
         H(targets[i]);
diff --git a/vscode/qsharp.schema.json b/vscode/qsharp.schema.json
index 274b7489..25b2b780 100644
--- a/vscode/qsharp.schema.json
+++ b/vscode/qsharp.schema.json
@@ -54,6 +54,8 @@
                   "deprecatedNewtype",
                   "deprecatedSet",
                   "discourageChainAssignment",
+                  "deprecatedAssignUpdateExpr",
+                  "deprecatedUpdateExpr",
                   "doubleEquality",
                   "needlessOperation",
                   "deprecatedFunctionConstructor",


Test patch:
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index 59289925..ba945f5b 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -62,6 +62,8 @@ mod given_interpreter {
     }
 
     mod without_sources {
+        use std::rc::Rc;
+
         use expect_test::expect;
         use indoc::indoc;
 
@@ -341,6 +343,36 @@ mod given_interpreter {
             is_unit_with_output(&result, &output, "before\nafter");
         }
 
+        #[test]
+        fn assign_array_index_expr_eval_in_order() {
+            let mut interpreter = get_interpreter();
+            let (result, output) = line(
+                &mut interpreter,
+                "mutable arr = [[[0, 1], [2, 3]], [[4, 5], [6, 7]]];",
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let (result, output) = line(
+                &mut interpreter,
+                "arr[{ Message(\"First Index\"); 0 }][{ Message(\"Second Index\"); 1 }][{ Message(\"Third Index\"); 1 }] = 13;",
+            );
+            is_unit_with_output(&result, &output, "First Index\nSecond Index\nThird Index");
+            let (result, output) = line(&mut interpreter, "arr");
+            is_only_value(
+                &result,
+                &output,
+                &Value::Array(Rc::new(vec![
+                    Value::Array(Rc::new(vec![
+                        Value::Array(Rc::new(vec![Value::Int(0), Value::Int(1)])),
+                        Value::Array(Rc::new(vec![Value::Int(2), Value::Int(13)])),
+                    ])),
+                    Value::Array(Rc::new(vec![
+                        Value::Array(Rc::new(vec![Value::Int(4), Value::Int(5)])),
+                        Value::Array(Rc::new(vec![Value::Int(6), Value::Int(7)])),
+                    ])),
+                ])),
+            );
+        }
+
         #[test]
         fn global_qubits() {
             let mut interpreter = get_interpreter();
diff --git a/compiler/qsc_linter/src/tests.rs b/compiler/qsc_linter/src/tests.rs
index f40b2ec6..ae2ac6fe 100644
--- a/compiler/qsc_linter/src/tests.rs
+++ b/compiler/qsc_linter/src/tests.rs
@@ -766,6 +766,56 @@ fn needless_operation_inside_function_call() {
     );
 }
 
+#[test]
+fn deprecated_update_expr_lint() {
+    check(
+        &wrap_in_callable(
+            "mutable arr = []; arr w/ idx <- 42;",
+            CallableKind::Function,
+        ),
+        &expect![[r#"
+            [
+                SrcLint {
+                    source: "arr w/ idx <- 42",
+                    level: Allow,
+                    message: "deprecated use of update expressions",
+                    help: "update expressions \"a w/ b <- c\" are deprecated; consider using explicit assignment instead",
+                    code_action_edits: [],
+                },
+            ]
+        "#]],
+    );
+}
+
+#[test]
+fn deprecated_assign_update_expr_code_action() {
+    check(
+        &wrap_in_callable(
+            "mutable arr = []; arr w/= idx <- 42;",
+            CallableKind::Function,
+        ),
+        &expect![[r#"
+            [
+                SrcLint {
+                    source: "arr w/= idx <- 42",
+                    level: Allow,
+                    message: "deprecated use of update assignment expressions",
+                    help: "update assignment expressions \"a w/= b <- c\" are deprecated; consider using explicit assignment instead \"a[b] = c\"",
+                    code_action_edits: [
+                        (
+                            "arr[idx] = 42",
+                            Span {
+                                lo: 89,
+                                hi: 106,
+                            },
+                        ),
+                    ],
+                },
+            ]
+        "#]],
+    );
+}
+
 #[test]
 fn check_that_hir_lints_are_deduplicated_in_operations_with_multiple_specializations() {
     check_with_deduplication(
diff --git a/compiler/qsc_passes/src/index_assignment/tests.rs b/compiler/qsc_passes/src/index_assignment/tests.rs
new file mode 100644
index 00000000..77a97d12
--- /dev/null
+++ b/compiler/qsc_passes/src/index_assignment/tests.rs
@@ -0,0 +1,403 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#![allow(clippy::too_many_lines)]
+
+use expect_test::{expect, Expect};
+use indoc::indoc;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
+use qsc_hir::{mut_visit::MutVisitor, validate::Validator, visit::Visitor};
+
+use crate::index_assignment::ConvertToWSlash;
+
+fn check(file: &str, expect: &Expect) {
+    let store = PackageStore::new(compile::core());
+    let sources = SourceMap::new([("test".into(), file.into())], None);
+    let mut unit = compile(
+        &store,
+        &[],
+        sources,
+        TargetCapabilityFlags::all(),
+        LanguageFeatures::default(),
+    );
+    assert!(unit.errors.is_empty(), "{:?}", unit.errors);
+    ConvertToWSlash {
+        assigner: &mut unit.assigner,
+    }
+    .visit_package(&mut unit.package);
+    Validator::default().visit_package(&unit.package);
+    expect.assert_eq(&unit.package.to_string());
+}
+
+#[test]
+fn convert_array_assign() {
+    check(
+        indoc! {r#"
+        operation Main() : Unit {
+            mutable arr = [0, 1];
+            arr[0] = 3;
+        }
+        "#},
+        &expect![[r#"
+            Package:
+                Item 0 [0-69] (Public):
+                    Namespace (Ident 17 [0-69] "test"): Item 1
+                Item 1 [0-69] (Internal):
+                    Parent: 0
+                    Callable 0 [0-69] (operation):
+                        name: Ident 1 [10-14] "Main"
+                        input: Pat 2 [14-16] [Type Unit]: Unit
+                        output: Unit
+                        functors: empty set
+                        body: SpecDecl 3 [0-69]: Impl:
+                            Block 4 [24-69] [Type Unit]:
+                                Stmt 5 [30-51]: Local (Mutable):
+                                    Pat 6 [38-41] [Type Int[]]: Bind: Ident 7 [38-41] "arr"
+                                    Expr 8 [44-50] [Type Int[]]: Array:
+                                        Expr 9 [45-46] [Type Int]: Lit: Int(0)
+                                        Expr 10 [48-49] [Type Int]: Lit: Int(1)
+                                Stmt 11 [56-67]: Semi: Expr 25 [56-66] [Type Unit]: Expr Block: Block 26 [56-66] [Type Unit]:
+                                    Stmt 19 [0-0]: Local (Immutable):
+                                        Pat 20 [60-61] [Type Int]: Bind: Ident 18 [60-61] "@index_18"
+                                        Expr 15 [60-61] [Type Int]: Lit: Int(0)
+                                    Stmt 24 [0-0]: Expr: Expr 22 [0-0] [Type Unit]: AssignIndex:
+                                        Expr 21 [56-59] [Type Int[]]: Var: Local 7
+                                        Expr 23 [60-61] [Type Int]: Var: Local 18
+                                        Expr 16 [65-66] [Type Int]: Lit: Int(3)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>"#]],
+    );
+}
+
+#[test]
+fn convert_2d_array_assign() {
+    check(
+        indoc! {r#"
+        operation Main() : Unit {
+            mutable arr = [[0, 1], [2, 3]];
+            arr[0][1] = 3;
+        }
+        "#},
+        &expect![[r#"
+            Package:
+                Item 0 [0-82] (Public):
+                    Namespace (Ident 23 [0-82] "test"): Item 1
+                Item 1 [0-82] (Internal):
+                    Parent: 0
+                    Callable 0 [0-82] (operation):
+                        name: Ident 1 [10-14] "Main"
+                        input: Pat 2 [14-16] [Type Unit]: Unit
+                        output: Unit
+                        functors: empty set
+                        body: SpecDecl 3 [0-82]: Impl:
+                            Block 4 [24-82] [Type Unit]:
+                                Stmt 5 [30-61]: Local (Mutable):
+                                    Pat 6 [38-41] [Type Int[][]]: Bind: Ident 7 [38-41] "arr"
+                                    Expr 8 [44-60] [Type Int[][]]: Array:
+                                        Expr 9 [45-51] [Type Int[]]: Array:
+                                            Expr 10 [46-47] [Type Int]: Lit: Int(0)
+                                            Expr 11 [49-50] [Type Int]: Lit: Int(1)
+                                        Expr 12 [53-59] [Type Int[]]: Array:
+                                            Expr 13 [54-55] [Type Int]: Lit: Int(2)
+                                            Expr 14 [57-58] [Type Int]: Lit: Int(3)
+                                Stmt 15 [66-80]: Semi: Expr 39 [66-79] [Type Unit]: Expr Block: Block 40 [66-79] [Type Unit]:
+                                    Stmt 25 [0-0]: Local (Immutable):
+                                        Pat 26 [70-71] [Type Int]: Bind: Ident 24 [70-71] "@index_24"
+                                        Expr 20 [70-71] [Type Int]: Lit: Int(0)
+                                    Stmt 28 [0-0]: Local (Immutable):
+                                        Pat 29 [73-74] [Type Int]: Bind: Ident 27 [73-74] "@index_27"
+                                        Expr 21 [73-74] [Type Int]: Lit: Int(1)
+                                    Stmt 38 [0-0]: Expr: Expr 36 [0-0] [Type Unit]: AssignIndex:
+                                        Expr 35 [66-69] [Type Int[][]]: Var: Local 7
+                                        Expr 37 [70-71] [Type Int]: Var: Local 24
+                                        Expr 33 [0-0] [Type Int[]]: UpdateIndex:
+                                            Expr 31 [0-0] [Type Int[]]: Index:
+                                                Expr 30 [66-69] [Type Int[][]]: Var: Local 7
+                                                Expr 32 [70-71] [Type Int]: Var: Local 24
+                                            Expr 34 [73-74] [Type Int]: Var: Local 27
+                                            Expr 22 [78-79] [Type Int]: Lit: Int(3)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>"#]],
+    );
+}
+
+#[test]
+fn convert_3d_array_assign() {
+    check(
+        indoc! {r#"
+        operation Main() : Unit {
+            mutable arr = [[[0b000, 0b001], [0b010, 0b011]], [[0b100, 0b101], [0b110, 0b111]]];
+            arr[0][1][1] = 0b1111;
+        }
+        "#},
+        &expect![[r#"
+            Package:
+                Item 0 [0-142] (Public):
+                    Namespace (Ident 33 [0-142] "test"): Item 1
+                Item 1 [0-142] (Internal):
+                    Parent: 0
+                    Callable 0 [0-142] (operation):
+                        name: Ident 1 [10-14] "Main"
+                        input: Pat 2 [14-16] [Type Unit]: Unit
+                        output: Unit
+                        functors: empty set
+                        body: SpecDecl 3 [0-142]: Impl:
+                            Block 4 [24-142] [Type Unit]:
+                                Stmt 5 [30-113]: Local (Mutable):
+                                    Pat 6 [38-41] [Type Int[][][]]: Bind: Ident 7 [38-41] "arr"
+                                    Expr 8 [44-112] [Type Int[][][]]: Array:
+                                        Expr 9 [45-77] [Type Int[][]]: Array:
+                                            Expr 10 [46-60] [Type Int[]]: Array:
+                                                Expr 11 [47-52] [Type Int]: Lit: Int(0)
+                                                Expr 12 [54-59] [Type Int]: Lit: Int(1)
+                                            Expr 13 [62-76] [Type Int[]]: Array:
+                                                Expr 14 [63-68] [Type Int]: Lit: Int(2)
+                                                Expr 15 [70-75] [Type Int]: Lit: Int(3)
+                                        Expr 16 [79-111] [Type Int[][]]: Array:
+                                            Expr 17 [80-94] [Type Int[]]: Array:
+                                                Expr 18 [81-86] [Type Int]: Lit: Int(4)
+                                                Expr 19 [88-93] [Type Int]: Lit: Int(5)
+                                            Expr 20 [96-110] [Type Int[]]: Array:
+                                                Expr 21 [97-102] [Type Int]: Lit: Int(6)
+                                                Expr 22 [104-109] [Type Int]: Lit: Int(7)
+                                Stmt 23 [118-140]: Semi: Expr 59 [118-139] [Type Unit]: Expr Block: Block 60 [118-139] [Type Unit]:
+                                    Stmt 35 [0-0]: Local (Immutable):
+                                        Pat 36 [122-123] [Type Int]: Bind: Ident 34 [122-123] "@index_34"
+                                        Expr 29 [122-123] [Type Int]: Lit: Int(0)
+                                    Stmt 38 [0-0]: Local (Immutable):
+                                        Pat 39 [125-126] [Type Int]: Bind: Ident 37 [125-126] "@index_37"
+                                        Expr 30 [125-126] [Type Int]: Lit: Int(1)
+                                    Stmt 41 [0-0]: Local (Immutable):
+                                        Pat 42 [128-129] [Type Int]: Bind: Ident 40 [128-129] "@index_40"
+                                        Expr 31 [128-129] [Type Int]: Lit: Int(1)
+                                    Stmt 58 [0-0]: Expr: Expr 56 [0-0] [Type Unit]: AssignIndex:
+                                        Expr 55 [118-121] [Type Int[][][]]: Var: Local 7
+                                        Expr 57 [122-123] [Type Int]: Var: Local 34
+                                        Expr 53 [0-0] [Type Int[][]]: UpdateIndex:
+                                            Expr 51 [0-0] [Type Int[][]]: Index:
+                                                Expr 50 [118-121] [Type Int[][][]]: Var: Local 7
+                                                Expr 52 [122-123] [Type Int]: Var: Local 34
+                                            Expr 54 [125-126] [Type Int]: Var: Local 37
+                                            Expr 48 [0-0] [Type Int[]]: UpdateIndex:
+                                                Expr 46 [0-0] [Type Int[]]: Index:
+                                                    Expr 44 [0-0] [Type Int[][]]: Index:
+                                                        Expr 43 [118-121] [Type Int[][][]]: Var: Local 7
+                                                        Expr 45 [122-123] [Type Int]: Var: Local 34
+                                                    Expr 47 [125-126] [Type Int]: Var: Local 37
+                                                Expr 49 [128-129] [Type Int]: Var: Local 40
+                                                Expr 32 [133-139] [Type Int]: Lit: Int(15)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>"#]],
+    );
+}
+
+#[test]
+fn convert_array_assign_range() {
+    check(
+        indoc! {r#"
+        operation Main() : Unit {
+            mutable arr = [0, 1, 2, 3];
+            arr[1..3] = [4, 5, 6];
+        }
+        "#},
+        &expect![[r#"
+            Package:
+                Item 0 [0-86] (Public):
+                    Namespace (Ident 24 [0-86] "test"): Item 1
+                Item 1 [0-86] (Internal):
+                    Parent: 0
+                    Callable 0 [0-86] (operation):
+                        name: Ident 1 [10-14] "Main"
+                        input: Pat 2 [14-16] [Type Unit]: Unit
+                        output: Unit
+                        functors: empty set
+                        body: SpecDecl 3 [0-86]: Impl:
+                            Block 4 [24-86] [Type Unit]:
+                                Stmt 5 [30-57]: Local (Mutable):
+                                    Pat 6 [38-41] [Type Int[]]: Bind: Ident 7 [38-41] "arr"
+                                    Expr 8 [44-56] [Type Int[]]: Array:
+                                        Expr 9 [45-46] [Type Int]: Lit: Int(0)
+                                        Expr 10 [48-49] [Type Int]: Lit: Int(1)
+                                        Expr 11 [51-52] [Type Int]: Lit: Int(2)
+                                        Expr 12 [54-55] [Type Int]: Lit: Int(3)
+                                Stmt 13 [62-84]: Semi: Expr 32 [62-83] [Type Unit]: Expr Block: Block 33 [62-83] [Type Unit]:
+                                    Stmt 26 [0-0]: Local (Immutable):
+                                        Pat 27 [66-70] [Type Range]: Bind: Ident 25 [66-70] "@index_25"
+                                        Expr 17 [66-70] [Type Range]: Range:
+                                            Expr 18 [66-67] [Type Int]: Lit: Int(1)
+                                            <no step>
+                                            Expr 19 [69-70] [Type Int]: Lit: Int(3)
+                                    Stmt 31 [0-0]: Expr: Expr 29 [0-0] [Type Unit]: AssignIndex:
+                                        Expr 28 [62-65] [Type Int[]]: Var: Local 7
+                                        Expr 30 [66-70] [Type Range]: Var: Local 25
+                                        Expr 20 [74-83] [Type Int[]]: Array:
+                                            Expr 21 [75-76] [Type Int]: Lit: Int(4)
+                                            Expr 22 [78-79] [Type Int]: Lit: Int(5)
+                                            Expr 23 [81-82] [Type Int]: Lit: Int(6)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>"#]],
+    );
+}
+
+#[test]
+fn convert_array_assign_single_with_range() {
+    check(
+        indoc! {r#"
+        operation Main() : Unit {
+            mutable arr = [0, 1, 2, 3];
+            arr[1..3][1] = 3;
+        }
+        "#},
+        &expect![[r#"
+            Package:
+                Item 0 [0-81] (Public):
+                    Namespace (Ident 23 [0-81] "test"): Item 1
+                Item 1 [0-81] (Internal):
+                    Parent: 0
+                    Callable 0 [0-81] (operation):
+                        name: Ident 1 [10-14] "Main"
+                        input: Pat 2 [14-16] [Type Unit]: Unit
+                        output: Unit
+                        functors: empty set
+                        body: SpecDecl 3 [0-81]: Impl:
+                            Block 4 [24-81] [Type Unit]:
+                                Stmt 5 [30-57]: Local (Mutable):
+                                    Pat 6 [38-41] [Type Int[]]: Bind: Ident 7 [38-41] "arr"
+                                    Expr 8 [44-56] [Type Int[]]: Array:
+                                        Expr 9 [45-46] [Type Int]: Lit: Int(0)
+                                        Expr 10 [48-49] [Type Int]: Lit: Int(1)
+                                        Expr 11 [51-52] [Type Int]: Lit: Int(2)
+                                        Expr 12 [54-55] [Type Int]: Lit: Int(3)
+                                Stmt 13 [62-79]: Semi: Expr 39 [62-78] [Type Unit]: Expr Block: Block 40 [62-78] [Type Unit]:
+                                    Stmt 25 [0-0]: Local (Immutable):
+                                        Pat 26 [66-70] [Type Range]: Bind: Ident 24 [66-70] "@index_24"
+                                        Expr 18 [66-70] [Type Range]: Range:
+                                            Expr 19 [66-67] [Type Int]: Lit: Int(1)
+                                            <no step>
+                                            Expr 20 [69-70] [Type Int]: Lit: Int(3)
+                                    Stmt 28 [0-0]: Local (Immutable):
+                                        Pat 29 [72-73] [Type Int]: Bind: Ident 27 [72-73] "@index_27"
+                                        Expr 21 [72-73] [Type Int]: Lit: Int(1)
+                                    Stmt 38 [0-0]: Expr: Expr 36 [0-0] [Type Unit]: AssignIndex:
+                                        Expr 35 [62-65] [Type Int[]]: Var: Local 7
+                                        Expr 37 [66-70] [Type Range]: Var: Local 24
+                                        Expr 33 [0-0] [Type Int[]]: UpdateIndex:
+                                            Expr 31 [0-0] [Type Int[]]: Index:
+                                                Expr 30 [62-65] [Type Int[]]: Var: Local 7
+                                                Expr 32 [66-70] [Type Range]: Var: Local 24
+                                            Expr 34 [72-73] [Type Int]: Var: Local 27
+                                            Expr 22 [77-78] [Type Int]: Lit: Int(3)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>"#]],
+    );
+}
+
+#[test]
+fn convert_array_assign_op() {
+    check(
+        indoc! {r#"
+        operation Main() : Unit {
+            mutable arr = [0, 1];
+            arr[0] += 3;
+        }
+        "#},
+        &expect![[r#"
+            Package:
+                Item 0 [0-70] (Public):
+                    Namespace (Ident 17 [0-70] "test"): Item 1
+                Item 1 [0-70] (Internal):
+                    Parent: 0
+                    Callable 0 [0-70] (operation):
+                        name: Ident 1 [10-14] "Main"
+                        input: Pat 2 [14-16] [Type Unit]: Unit
+                        output: Unit
+                        functors: empty set
+                        body: SpecDecl 3 [0-70]: Impl:
+                            Block 4 [24-70] [Type Unit]:
+                                Stmt 5 [30-51]: Local (Mutable):
+                                    Pat 6 [38-41] [Type Int[]]: Bind: Ident 7 [38-41] "arr"
+                                    Expr 8 [44-50] [Type Int[]]: Array:
+                                        Expr 9 [45-46] [Type Int]: Lit: Int(0)
+                                        Expr 10 [48-49] [Type Int]: Lit: Int(1)
+                                Stmt 11 [56-68]: Semi: Expr 29 [56-67] [Type Unit]: Expr Block: Block 30 [56-67] [Type Unit]:
+                                    Stmt 19 [0-0]: Local (Immutable):
+                                        Pat 20 [60-61] [Type Int]: Bind: Ident 18 [60-61] "@index_18"
+                                        Expr 15 [60-61] [Type Int]: Lit: Int(0)
+                                    Stmt 28 [0-0]: Expr: Expr 26 [0-0] [Type Unit]: AssignIndex:
+                                        Expr 25 [56-59] [Type Int[]]: Var: Local 7
+                                        Expr 27 [60-61] [Type Int]: Var: Local 18
+                                        Expr 24 [56-67] [Type Unit]: BinOp (Add):
+                                            Expr 22 [0-0] [Type Int]: Index:
+                                                Expr 21 [56-59] [Type Int[]]: Var: Local 7
+                                                Expr 23 [60-61] [Type Int]: Var: Local 18
+                                            Expr 16 [66-67] [Type Int]: Lit: Int(3)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>"#]],
+    );
+}
+
+#[test]
+fn convert_2d_array_assign_op() {
+    check(
+        indoc! {r#"
+        operation Main() : Unit {
+            mutable arr = [[0, 1], [2, 3]];
+            arr[0][1] *= 2;
+        }
+        "#},
+        &expect![[r#"
+            Package:
+                Item 0 [0-83] (Public):
+                    Namespace (Ident 23 [0-83] "test"): Item 1
+                Item 1 [0-83] (Internal):
+                    Parent: 0
+                    Callable 0 [0-83] (operation):
+                        name: Ident 1 [10-14] "Main"
+                        input: Pat 2 [14-16] [Type Unit]: Unit
+                        output: Unit
+                        functors: empty set
+                        body: SpecDecl 3 [0-83]: Impl:
+                            Block 4 [24-83] [Type Unit]:
+                                Stmt 5 [30-61]: Local (Mutable):
+                                    Pat 6 [38-41] [Type Int[][]]: Bind: Ident 7 [38-41] "arr"
+                                    Expr 8 [44-60] [Type Int[][]]: Array:
+                                        Expr 9 [45-51] [Type Int[]]: Array:
+                                            Expr 10 [46-47] [Type Int]: Lit: Int(0)
+                                            Expr 11 [49-50] [Type Int]: Lit: Int(1)
+                                        Expr 12 [53-59] [Type Int[]]: Array:
+                                            Expr 13 [54-55] [Type Int]: Lit: Int(2)
+                                            Expr 14 [57-58] [Type Int]: Lit: Int(3)
+                                Stmt 15 [66-81]: Semi: Expr 45 [66-80] [Type Unit]: Expr Block: Block 46 [66-80] [Type Unit]:
+                                    Stmt 25 [0-0]: Local (Immutable):
+                                        Pat 26 [70-71] [Type Int]: Bind: Ident 24 [70-71] "@index_24"
+                                        Expr 20 [70-71] [Type Int]: Lit: Int(0)
+                                    Stmt 28 [0-0]: Local (Immutable):
+                                        Pat 29 [73-74] [Type Int]: Bind: Ident 27 [73-74] "@index_27"
+                                        Expr 21 [73-74] [Type Int]: Lit: Int(1)
+                                    Stmt 44 [0-0]: Expr: Expr 42 [0-0] [Type Unit]: AssignIndex:
+                                        Expr 41 [66-69] [Type Int[][]]: Var: Local 7
+                                        Expr 43 [70-71] [Type Int]: Var: Local 24
+                                        Expr 39 [0-0] [Type Int[]]: UpdateIndex:
+                                            Expr 37 [0-0] [Type Int[]]: Index:
+                                                Expr 36 [66-69] [Type Int[][]]: Var: Local 7
+                                                Expr 38 [70-71] [Type Int]: Var: Local 24
+                                            Expr 40 [73-74] [Type Int]: Var: Local 27
+                                            Expr 35 [66-80] [Type Unit]: BinOp (Mul):
+                                                Expr 33 [0-0] [Type Int]: Index:
+                                                    Expr 31 [0-0] [Type Int[]]: Index:
+                                                        Expr 30 [66-69] [Type Int[][]]: Var: Local 7
+                                                        Expr 32 [70-71] [Type Int]: Var: Local 24
+                                                    Expr 34 [73-74] [Type Int]: Var: Local 27
+                                                Expr 22 [79-80] [Type Int]: Lit: Int(2)
+                        adj: <none>
+                        ctl: <none>
+                        ctl-adj: <none>"#]],
+    );
+}