Repository: noir-lang/noir (2025-07-18T15:26:06Z)

Issue:
Comptime disagrees with ACIR on handling mutable references to tuple elements
### Aim

The following program:
```
fn main() -> pub () {
    comptime {
        let mut a = (false,);
        let b = &mut a.0;
        a = (true,);
        if (*b) {
            println("foo");
        }
    }
}
```

prints nothing. However, running it without `comptime` prints out "foo".

### Expected Behavior

Comptime and ACIR versions should agree on printed output.

### Bug

```
$ nargo execute --force
[test1] Circuit witness successfully solved
[test1] Witness saved to target/test1.gz
```

After removing `comptime`:
```
foo
[test1] Circuit witness successfully solved
[test1] Witness saved to target/test1.gz
```

### To Reproduce

1.
2.
3.
4.


### Workaround

None

### Workaround Description

_No response_

### Additional Context

_No response_

### Project Impact

None

### Blocker Context

_No response_

### Nargo Version

_No response_

### NoirJS Version

_No response_

### Proving Backend Tooling & Version

_No response_

### Would you like to submit a PR for this Issue?

None

### Support Needs

_No response_


Discussion:

Patch:
diff --git a/compiler/noirc_frontend/src/hir/comptime/display.rs b/compiler/noirc_frontend/src/hir/comptime/display.rs
index 70b11d18b0e..63537b1f27c 100644
--- a/compiler/noirc_frontend/src/hir/comptime/display.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/display.rs
@@ -386,7 +386,8 @@ impl Display for ValuePrinter<'_, '_> {
             Value::Function(..) => write!(f, "(function)"),
             Value::Closure(..) => write!(f, "(closure)"),
             Value::Tuple(fields) => {
-                let fields = vecmap(fields, |field| field.display(self.interner).to_string());
+                let fields =
+                    vecmap(fields, |field| field.borrow().display(self.interner).to_string());
                 if fields.len() == 1 {
                     write!(f, "({},)", fields[0])
                 } else {
@@ -399,7 +400,7 @@ impl Display for ValuePrinter<'_, '_> {
                     other => other.to_string(),
                 };
                 let fields = vecmap(fields, |(name, value)| {
-                    format!("{}: {}", name, value.display(self.interner))
+                    format!("{}: {}", name, value.borrow().display(self.interner))
                 });
                 write!(f, "{typename} {{ {} }}", fields.join(", "))
             }
diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs
index abbc78d3e32..5f97421774e 100644
--- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs
@@ -422,6 +422,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
                         for ((pattern, typ), argument) in
                             pattern_fields.iter().zip(type_fields).zip(fields)
                         {
+                            let argument = argument.borrow().clone();
                             self.define_pattern(pattern, typ, argument, location)?;
                         }
                         Ok(())
@@ -450,6 +451,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
                                 }
                             })?;
 
+                            let field = field.borrow();
                             let field_type = field.get_type().into_owned();
                             let result = self.define_pattern(
                                 field_pattern,
@@ -492,7 +494,10 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
             if let Entry::Occupied(mut entry) = scope.entry(id) {
                 match entry.get() {
                     Value::Pointer(reference, true, _) => {
-                        *reference.borrow_mut() = argument;
+                        // We can't store to the reference directly, we need to check if the value
+                        // is a struct or tuple to store to each field instead. This is so any
+                        // references to these fields are also updated.
+                        Self::store_flattened(reference.clone(), argument);
                     }
                     _ => {
                         entry.insert(argument);
@@ -898,8 +903,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
     /// - `<=`: `ordering != Ordering::Less`
     fn evaluate_ordering(&self, ordering: Value, operator: BinaryOpKind) -> IResult<Value> {
         let ordering = match ordering {
-            Value::Struct(fields, _) => match fields.into_iter().next().unwrap().1 {
-                Value::Field(ordering) => ordering,
+            Value::Struct(fields, _) => match &*fields.into_iter().next().unwrap().1.borrow() {
+                Value::Field(ordering) => *ordering,
                 _ => unreachable!("`cmp` should always return an Ordering value"),
             },
             _ => unreachable!("`cmp` should always return an Ordering value"),
@@ -938,7 +943,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
             .fields
             .into_iter()
             .map(|(name, expr)| {
-                let field_value = self.evaluate(expr)?;
+                let field_value = Shared::new(self.evaluate(expr)?);
                 Ok((Rc::new(name.into_string()), field_value))
             })
             .collect::<Result<_, _>>()?;
@@ -961,11 +966,11 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
         let (fields, struct_type) = match self.evaluate(access.lhs)? {
             Value::Struct(fields, typ) => (fields, typ),
             Value::Tuple(fields) => {
-                let (fields, field_types): (HashMap<Rc<String>, Value>, Vec<Type>) = fields
+                let (fields, field_types): (HashMap<Rc<String>, Shared<Value>>, Vec<Type>) = fields
                     .into_iter()
                     .enumerate()
                     .map(|(i, field)| {
-                        let field_type = field.get_type().into_owned();
+                        let field_type = field.borrow().get_type().into_owned();
                         let key_val_pair = (Rc::new(i.to_string()), field);
                         (key_val_pair, field_type)
                     })
@@ -979,19 +984,25 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
             }
         };
 
-        fields.get(access.rhs.as_string()).cloned().ok_or_else(|| {
+        let field = fields.get(access.rhs.as_string()).cloned().ok_or_else(|| {
             let location = self.elaborator.interner.expr_location(&id);
             let value = Value::Struct(fields, struct_type);
             let field_name = access.rhs.into_string();
             let typ = value.get_type().into_owned();
             InterpreterError::ExpectedStructToHaveField { typ, field_name, location }
-        })
+        })?;
+
+        // Return a reference to the field so that `&mut foo.bar.baz` can use this reference.
+        // We set auto_deref to true so that when it is used elsewhere it is dereferenced
+        // automatically.
+        Ok(Value::Pointer(field, true, false))
     }
 
     fn evaluate_call(&mut self, call: HirCallExpression, id: ExprId) -> IResult<Value> {
         let function = self.evaluate(call.func)?;
         let arguments = try_vecmap(call.arguments, |arg| {
-            Ok((self.evaluate(arg)?, self.elaborator.interner.expr_location(&arg)))
+            let value = self.evaluate(arg)?.copy();
+            Ok((value, self.elaborator.interner.expr_location(&arg)))
         })?;
         let location = self.elaborator.interner.expr_location(&id);
 
@@ -1084,7 +1095,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
     }
 
     fn evaluate_tuple(&mut self, tuple: Vec<ExprId>) -> IResult<Value> {
-        let fields = try_vecmap(tuple, |field| self.evaluate(field))?;
+        let fields = try_vecmap(tuple, |field| Ok(Shared::new(self.evaluate(field)?)))?;
         Ok(Value::Tuple(fields))
     }
 
@@ -1165,7 +1176,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
             HirLValue::Dereference { lvalue, element_type: _, location, implicitly_added: _ } => {
                 match self.evaluate_lvalue(&lvalue)? {
                     Value::Pointer(value, _, _) => {
-                        *value.borrow_mut() = rhs;
+                        Self::store_flattened(value, rhs);
                         Ok(())
                     }
                     value => {
@@ -1186,11 +1197,11 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
 
                 match object_value {
                     Value::Tuple(mut fields) => {
-                        fields[index] = rhs;
+                        fields[index] = Shared::new(rhs);
                         self.store_lvalue(*object, Value::Tuple(fields))
                     }
                     Value::Struct(mut fields, typ) => {
-                        fields.insert(Rc::new(field_name.into_string()), rhs);
+                        fields.insert(Rc::new(field_name.into_string()), Shared::new(rhs));
                         self.store_lvalue(*object, Value::Struct(fields, typ.follow_bindings()))
                     }
                     value => {
@@ -1217,6 +1228,35 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
         }
     }
 
+    /// When we store to a struct such as in
+    /// ```noir
+    /// let mut a = (false,);
+    /// let b = &mut a.0;
+    /// a = (true,);
+    /// ```
+    /// we must flatten the store to store to each individual field so that any existing
+    /// references, such as `b` above, will also reflect the mutation.
+    fn store_flattened(lvalue: Shared<Value>, rvalue: Value) {
+        let lvalue_ref = lvalue.borrow();
+        match (&*lvalue_ref, rvalue) {
+            (Value::Struct(lvalue_fields, _), Value::Struct(mut rvalue_fields, _)) => {
+                for (name, lvalue) in lvalue_fields.iter() {
+                    let Some(rvalue) = rvalue_fields.remove(name) else { continue };
+                    Self::store_flattened(lvalue.clone(), rvalue.unwrap_or_clone());
+                }
+            }
+            (Value::Tuple(lvalue_fields), Value::Tuple(rvalue_fields)) => {
+                for (lvalue, rvalue) in lvalue_fields.iter().zip(rvalue_fields) {
+                    Self::store_flattened(lvalue.clone(), rvalue.unwrap_or_clone());
+                }
+            }
+            (_, rvalue) => {
+                drop(lvalue_ref);
+                *lvalue.borrow_mut() = rvalue;
+            }
+        }
+    }
+
     fn evaluate_lvalue(&mut self, lvalue: &HirLValue) -> IResult<Value> {
         match lvalue {
             HirLValue::Ident(ident, _) => match self.lookup(ident)? {
@@ -1244,8 +1284,10 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
                 })?;
 
                 match object_value {
-                    Value::Tuple(mut values) => Ok(values.swap_remove(index)),
-                    Value::Struct(fields, _) => Ok(fields[field_name.as_string()].clone()),
+                    Value::Tuple(mut values) => Ok(values.swap_remove(index).unwrap_or_clone()),
+                    Value::Struct(fields, _) => {
+                        Ok(fields[field_name.as_string()].clone().unwrap_or_clone())
+                    }
                     value => Err(InterpreterError::NonTupleOrStructInMemberAccess {
                         typ: value.get_type().into_owned(),
                         location: *location,
diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
index 84186cce80c..31a2903c99f 100644
--- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
@@ -509,8 +509,8 @@ fn type_def_generics(
                 Kind::Numeric(numeric_type) => Some(Value::Type(*numeric_type)),
                 _ => None,
             };
-            let numeric_type = option(option_typ.clone(), numeric_type, location);
-            Value::Tuple(vec![Value::Type(generic_as_named), numeric_type])
+            let numeric_type = Shared::new(option(option_typ.clone(), numeric_type, location));
+            Value::Tuple(vec![Shared::new(Value::Type(generic_as_named)), numeric_type])
         })
         .collect();
 
@@ -576,9 +576,10 @@ fn type_def_fields(
     if let Some(struct_fields) = struct_def.get_fields(&generic_args) {
         for (field_name, field_type, visibility) in struct_fields {
             let token = LocatedToken::new(Token::Ident(field_name), location);
-            let name = Value::Quoted(Rc::new(vec![token]));
-            let visibility = visibility_to_quoted(visibility, location);
-            fields.push_back(Value::Tuple(vec![name, Value::Type(field_type), visibility]));
+            let name = Shared::new(Value::Quoted(Rc::new(vec![token])));
+            let field_type = Shared::new(Value::Type(field_type));
+            let visibility = Shared::new(visibility_to_quoted(visibility, location));
+            fields.push_back(Value::Tuple(vec![name, field_type, visibility]));
         }
     }
 
@@ -609,9 +610,10 @@ fn type_def_fields_as_written(
     if let Some(struct_fields) = struct_def.get_fields_as_written() {
         for field in struct_fields {
             let token = LocatedToken::new(Token::Ident(field.name.to_string()), location);
-            let name = Value::Quoted(Rc::new(vec![token]));
-            let typ = Value::Type(field.typ);
-            let visibility = visibility_to_quoted(field.visibility, location);
+            let name = Shared::new(Value::Quoted(Rc::new(vec![token])));
+
+            let typ = Shared::new(Value::Type(field.typ));
+            let visibility = Shared::new(visibility_to_quoted(field.visibility, location));
             fields.push_back(Value::Tuple(vec![name, typ, visibility]));
         }
     }
@@ -676,9 +678,9 @@ fn type_def_set_fields(
 
     for (index, mut field_pair) in fields {
         if field_pair.len() == 3 {
-            let visibility = field_pair.pop().unwrap();
-            let typ = field_pair.pop().unwrap();
-            let name_value = field_pair.pop().unwrap();
+            let visibility = field_pair.pop().unwrap().unwrap_or_clone();
+            let typ = field_pair.pop().unwrap().unwrap_or_clone();
+            let name_value = field_pair.pop().unwrap().unwrap_or_clone();
 
             let name_tokens = get_quoted((name_value.clone(), field_location))?;
             let typ = get_type((typ, field_location))?;
@@ -709,7 +711,8 @@ fn type_def_set_fields(
             let type_var = elaborator.interner.next_type_variable();
             let expected = Type::Tuple(vec![type_var.clone(), type_var]);
 
-            let actual = Type::Tuple(vecmap(&field_pair, |value| value.get_type().into_owned()));
+            let actual =
+                Type::Tuple(vecmap(&field_pair, |value| value.borrow().get_type().into_owned()));
 
             return Err(InterpreterError::TypeMismatch { expected, actual, location });
         }
@@ -742,8 +745,8 @@ fn slice_remove(
         return failing_constraint(message, location, call_stack);
     }
 
-    let element = values.remove(index);
-    Ok(Value::Tuple(vec![Value::Slice(values, typ), element]))
+    let element = Shared::new(values.remove(index));
+    Ok(Value::Tuple(vec![Shared::new(Value::Slice(values, typ)), element]))
 }
 
 fn slice_push_front(
@@ -768,7 +771,9 @@ fn slice_pop_front(
 
     let (mut values, typ) = get_slice(interner, argument)?;
     match values.pop_front() {
-        Some(element) => Ok(Value::Tuple(vec![element, Value::Slice(values, typ)])),
+        Some(element) => {
+            Ok(Value::Tuple(vec![Shared::new(element), Shared::new(Value::Slice(values, typ))]))
+        }
         None => failing_constraint("slice_pop_front called on empty slice", location, call_stack),
     }
 }
@@ -783,7 +788,9 @@ fn slice_pop_back(
 
     let (mut values, typ) = get_slice(interner, argument)?;
     match values.pop_back() {
-        Some(element) => Ok(Value::Tuple(vec![Value::Slice(values, typ), element])),
+        Some(element) => {
+            Ok(Value::Tuple(vec![Shared::new(Value::Slice(values, typ)), Shared::new(element)]))
+        }
         None => failing_constraint("slice_pop_back called on empty slice", location, call_stack),
     }
 }
@@ -1005,7 +1012,9 @@ fn type_as_array(
 ) -> IResult<Value> {
     type_as(arguments, return_type, location, |typ| {
         if let Type::Array(length, array_type) = typ {
-            Some(Value::Tuple(vec![Value::Type(*array_type), Value::Type(*length)]))
+            let array_type = Shared::new(Value::Type(*array_type));
+            let length_type = Shared::new(Value::Type(*length));
+            Some(Value::Tuple(vec![array_type, length_type]))
         } else {
             None
         }
@@ -1045,7 +1054,9 @@ fn type_as_integer(
 ) -> IResult<Value> {
     type_as(arguments, return_type, location, |typ| {
         if let Type::Integer(sign, bits) = typ {
-            Some(Value::Tuple(vec![Value::Bool(sign.is_signed()), Value::U8(bits.bit_size())]))
+            let sign = Shared::new(Value::Bool(sign.is_signed()));
+            let bit_size = Shared::new(Value::U8(bits.bit_size()));
+            Some(Value::Tuple(vec![sign, bit_size]))
         } else {
             None
         }
@@ -1094,11 +1105,11 @@ fn type_as_data_type(
     type_as(arguments, return_type, location, |typ| {
         if let Type::DataType(struct_type, generics) = typ {
             Some(Value::Tuple(vec![
-                Value::TypeDefinition(struct_type.borrow().id),
-                Value::Slice(
+                Shared::new(Value::TypeDefinition(struct_type.borrow().id)),
+                Shared::new(Value::Slice(
                     generics.into_iter().map(Value::Type).collect(),
                     Type::Slice(Box::new(Type::Quoted(QuotedType::Type))),
-                ),
+                )),
             ]))
         } else {
             None
@@ -1493,7 +1504,9 @@ fn zeroed(return_type: Type, location: Location) -> Value {
             }
         }
         Type::Unit => Value::Unit,
-        Type::Tuple(fields) => Value::Tuple(vecmap(fields, |field| zeroed(field, location))),
+        Type::Tuple(fields) => {
+            Value::Tuple(vecmap(fields, |field| Shared::new(zeroed(field, location))))
+        }
         Type::DataType(data_type, generics) => {
             let typ = data_type.borrow();
 
@@ -1501,7 +1514,7 @@ fn zeroed(return_type: Type, location: Location) -> Value {
                 let mut values = HashMap::default();
 
                 for (field_name, field_type, _) in fields {
-                    let field_value = zeroed(field_type, location);
+                    let field_value = Shared::new(zeroed(field_type, location));
                     values.insert(Rc::new(field_name), field_value);
                 }
 
@@ -1586,7 +1599,7 @@ fn expr_as_assert(
                 } else {
                     (Some(constrain.arguments.pop().unwrap()), constrain.arguments.pop().unwrap())
                 };
-                let predicate = Value::expression(predicate.kind);
+                let predicate = Shared::new(Value::expression(predicate.kind));
 
                 let option_type = extract_option_generic_type(return_type);
                 let Type::Tuple(mut tuple_types) = option_type else {
@@ -1596,7 +1609,7 @@ fn expr_as_assert(
 
                 let option_type = tuple_types.pop().unwrap();
                 let message = message.map(|msg| Value::expression(msg.kind));
-                let message = option(option_type, message, location);
+                let message = Shared::new(option(option_type, message, location));
 
                 Some(Value::Tuple(vec![predicate, message]))
             } else {
@@ -1631,8 +1644,8 @@ fn expr_as_assert_eq(
                     )
                 };
 
-                let lhs = Value::expression(lhs.kind);
-                let rhs = Value::expression(rhs.kind);
+                let lhs = Shared::new(Value::expression(lhs.kind));
+                let rhs = Shared::new(Value::expression(rhs.kind));
 
                 let option_type = extract_option_generic_type(return_type);
                 let Type::Tuple(mut tuple_types) = option_type else {
@@ -1642,7 +1655,7 @@ fn expr_as_assert_eq(
 
                 let option_type = tuple_types.pop().unwrap();
                 let message = message.map(|message| Value::expression(message.kind));
-                let message = option(option_type, message, location);
+                let message = Shared::new(option(option_type, message, location));
 
                 Some(Value::Tuple(vec![lhs, rhs, message]))
             } else {
@@ -1663,8 +1676,8 @@ fn expr_as_assign(
 ) -> IResult<Value> {
     expr_as(interner, arguments, return_type, location, |expr| {
         if let ExprValue::Statement(StatementKind::Assign(assign)) = expr {
-            let lhs = Value::lvalue(assign.lvalue);
-            let rhs = Value::expression(assign.expression.kind);
+            let lhs = Shared::new(Value::lvalue(assign.lvalue));
+            let rhs = Shared::new(Value::expression(assign.expression.kind));
             Some(Value::Tuple(vec![lhs, rhs]))
         } else {
             None
@@ -1689,9 +1702,9 @@ fn expr_as_binary_op(
 
             tuple_types.pop().unwrap();
             let binary_op_type = tuple_types.pop().unwrap();
-            let binary_op = new_binary_op(infix_expr.operator, binary_op_type);
-            let lhs = Value::expression(infix_expr.lhs.kind);
-            let rhs = Value::expression(infix_expr.rhs.kind);
+            let binary_op = Shared::new(new_binary_op(infix_expr.operator, binary_op_type));
+            let lhs = Shared::new(Value::expression(infix_expr.lhs.kind));
+            let rhs = Shared::new(Value::expression(infix_expr.rhs.kind));
             Some(Value::Tuple(vec![lhs, binary_op, rhs]))
         } else {
             None
@@ -1740,8 +1753,8 @@ fn expr_as_cast(
 ) -> IResult<Value> {
     expr_as(interner, arguments, return_type, location, |expr| {
         if let ExprValue::Expression(ExpressionKind::Cast(cast)) = expr {
-            let lhs = Value::expression(cast.lhs.kind);
-            let typ = Value::UnresolvedType(cast.r#type.typ);
+            let lhs = Shared::new(Value::expression(cast.lhs.kind));
+            let typ = Shared::new(Value::UnresolvedType(cast.r#type.typ));
             Some(Value::Tuple(vec![lhs, typ]))
         } else {
             None
@@ -1795,17 +1808,19 @@ fn expr_as_constructor(
 
     let option_value =
         if let ExprValue::Expression(ExpressionKind::Constructor(constructor)) = expr_value {
-            let typ = Value::UnresolvedType(constructor.typ.typ);
+            let typ = Shared::new(Value::UnresolvedType(constructor.typ.typ));
             let fields = constructor.fields.into_iter();
             let fields = fields.map(|(name, value)| {
-                Value::Tuple(vec![quote_ident(&name, location), Value::expression(value.kind)])
+                let ident = Shared::new(quote_ident(&name, location));
+                let expr = Shared::new(Value::expression(value.kind));
+                Value::Tuple(vec![ident, expr])
             });
             let fields = fields.collect();
             let fields_type = Type::Slice(Box::new(Type::Tuple(vec![
                 Type::Quoted(QuotedType::Quoted),
                 Type::Quoted(QuotedType::Expr),
             ])));
-            let fields = Value::Slice(fields, fields_type);
+            let fields = Shared::new(Value::Slice(fields, fields_type));
             Some(Value::Tuple(vec![typ, fields]))
         } else {
             None
@@ -1826,9 +1841,9 @@ fn expr_as_for(
             if let ForRange::Array(array) = for_statement.range {
                 let token = Token::Ident(for_statement.identifier.into_string());
                 let token = LocatedToken::new(token, location);
-                let identifier = Value::Quoted(Rc::new(vec![token]));
-                let array = Value::expression(array.kind);
-                let body = Value::expression(for_statement.block.kind);
+                let identifier = Shared::new(Value::Quoted(Rc::new(vec![token])));
+                let array = Shared::new(Value::expression(array.kind));
+                let body = Shared::new(Value::expression(for_statement.block.kind));
                 Some(Value::Tuple(vec![identifier, array, body]))
             } else {
                 None
@@ -1852,10 +1867,10 @@ fn expr_as_for_range(
                 let (from, to) = bounds.into_half_open();
                 let token = Token::Ident(for_statement.identifier.into_string());
                 let token = LocatedToken::new(token, location);
-                let identifier = Value::Quoted(Rc::new(vec![token]));
-                let from = Value::expression(from.kind);
-                let to = Value::expression(to.kind);
-                let body = Value::expression(for_statement.block.kind);
+                let identifier = Shared::new(Value::Quoted(Rc::new(vec![token])));
+                let from = Shared::new(Value::expression(from.kind));
+                let to = Shared::new(Value::expression(to.kind));
+                let body = Shared::new(Value::expression(for_statement.block.kind));
                 Some(Value::Tuple(vec![identifier, from, to, body]))
             } else {
                 None
@@ -1875,11 +1890,13 @@ fn expr_as_function_call(
 ) -> IResult<Value> {
     expr_as(interner, arguments, return_type, location, |expr| {
         if let ExprValue::Expression(ExpressionKind::Call(call_expression)) = expr {
-            let function = Value::expression(call_expression.func.kind);
+            let function = Shared::new(Value::expression(call_expression.func.kind));
             let arguments = call_expression.arguments.into_iter();
             let arguments = arguments.map(|argument| Value::expression(argument.kind)).collect();
-            let arguments =
-                Value::Slice(arguments, Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))));
+            let arguments = Shared::new(Value::Slice(
+                arguments,
+                Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))),
+            ));
             Some(Value::Tuple(vec![function, arguments]))
         } else {
             None
@@ -1911,9 +1928,9 @@ fn expr_as_if(
             );
 
             Some(Value::Tuple(vec![
-                Value::expression(if_expr.condition.kind),
-                Value::expression(if_expr.consequence.kind),
-                alternative,
+                Shared::new(Value::expression(if_expr.condition.kind)),
+                Shared::new(Value::expression(if_expr.consequence.kind)),
+                Shared::new(alternative),
             ]))
         } else {
             None
@@ -1931,8 +1948,8 @@ fn expr_as_index(
     expr_as(interner, arguments, return_type, location, |expr| {
         if let ExprValue::Expression(ExpressionKind::Index(index_expr)) = expr {
             Some(Value::Tuple(vec![
-                Value::expression(index_expr.collection.kind),
-                Value::expression(index_expr.index.kind),
+                Shared::new(Value::expression(index_expr.collection.kind)),
+                Shared::new(Value::expression(index_expr.index.kind)),
             ]))
         } else {
             None
@@ -1950,15 +1967,15 @@ fn expr_as_integer(
     expr_as(interner, arguments, return_type.clone(), location, |expr| match expr {
         ExprValue::Expression(ExpressionKind::Literal(Literal::Integer(field, _suffix))) => {
             Some(Value::Tuple(vec![
-                Value::Field(SignedField::positive(field.absolute_value())),
-                Value::Bool(field.is_negative()),
+                Shared::new(Value::Field(SignedField::positive(field.absolute_value()))),
+                Shared::new(Value::Bool(field.is_negative())),
             ]))
         }
         ExprValue::Expression(ExpressionKind::Resolved(id)) => {
             if let HirExpression::Literal(HirLiteral::Integer(field)) = interner.expression(&id) {
                 Some(Value::Tuple(vec![
-                    Value::Field(SignedField::positive(field.absolute_value())),
-                    Value::Bool(field.is_negative()),
+                    Shared::new(Value::Field(SignedField::positive(field.absolute_value()))),
+                    Shared::new(Value::Bool(field.is_negative())),
                 ]))
             } else {
                 None
@@ -1994,23 +2011,23 @@ fn expr_as_lambda(
                 .parameters
                 .into_iter()
                 .map(|(pattern, typ)| {
-                    let pattern = Value::pattern(pattern);
+                    let pattern = Shared::new(Value::pattern(pattern));
                     let typ = if let UnresolvedTypeData::Unspecified = typ.typ {
                         None
                     } else {
                         Some(Value::UnresolvedType(typ.typ))
                     };
-                    let typ = option(option_unresolved_type.clone(), typ, location);
+                    let typ = Shared::new(option(option_unresolved_type.clone(), typ, location));
                     Value::Tuple(vec![pattern, typ])
                 })
                 .collect();
-            let parameters = Value::Slice(
+            let parameters = Shared::new(Value::Slice(
                 parameters,
                 Type::Slice(Box::new(Type::Tuple(vec![
                     Type::Quoted(QuotedType::Expr),
                     Type::Quoted(QuotedType::UnresolvedType),
                 ]))),
-            );
+            ));
 
             let return_type = lambda.return_type.typ;
             let return_type = if let UnresolvedTypeData::Unspecified = return_type {
@@ -2019,9 +2036,9 @@ fn expr_as_lambda(
                 Some(return_type)
             };
             let return_type = return_type.map(Value::UnresolvedType);
-            let return_type = option(option_unresolved_type, return_type, location);
+            let return_type = Shared::new(option(option_unresolved_type, return_type, location));
 
-            let body = Value::expression(lambda.body.kind);
+            let body = Shared::new(Value::expression(lambda.body.kind));
 
             Some(Value::Tuple(vec![parameters, return_type, body]))
         } else {
@@ -2056,9 +2073,9 @@ fn expr_as_let(
             let typ = option(option_type, typ, location);
 
             Some(Value::Tuple(vec![
-                Value::pattern(let_statement.pattern),
-                typ,
-                Value::expression(let_statement.expression.kind),
+                Shared::new(Value::pattern(let_statement.pattern)),
+                Shared::new(typ),
+                Shared::new(Value::expression(let_statement.expression.kind)),
             ]))
         }
         _ => None,
@@ -2075,12 +2092,15 @@ fn expr_as_member_access(
     expr_as(interner, arguments, return_type, location, |expr| match expr {
         ExprValue::Expression(ExpressionKind::MemberAccess(member_access)) => {
             Some(Value::Tuple(vec![
-                Value::expression(member_access.lhs.kind),
-                quote_ident(&member_access.rhs, location),
+                Shared::new(Value::expression(member_access.lhs.kind)),
+                Shared::new(quote_ident(&member_access.rhs, location)),
             ]))
         }
         ExprValue::LValue(crate::ast::LValue::MemberAccess { object, field_name, location: _ }) => {
-            Some(Value::Tuple(vec![Value::lvalue(*object), quote_ident(&field_name, location)]))
+            Some(Value::Tuple(vec![
+                Shared::new(Value::lvalue(*object)),
+                Shared::new(quote_ident(&field_name, location)),
+            ]))
         }
         _ => None,
     })
@@ -2095,21 +2115,23 @@ fn expr_as_method_call(
 ) -> IResult<Value> {
     expr_as(interner, arguments, return_type, location, |expr| {
         if let ExprValue::Expression(ExpressionKind::MethodCall(method_call)) = expr {
-            let object = Value::expression(method_call.object.kind);
+            let object = Shared::new(Value::expression(method_call.object.kind));
 
-            let name = quote_ident(&method_call.method_name, location);
+            let name = Shared::new(quote_ident(&method_call.method_name, location));
 
             let generics = method_call.generics.unwrap_or_default().into_iter();
             let generics = generics.map(|generic| Value::UnresolvedType(generic.typ)).collect();
-            let generics = Value::Slice(
+            let generics = Shared::new(Value::Slice(
                 generics,
                 Type::Slice(Box::new(Type::Quoted(QuotedType::UnresolvedType))),
-            );
+            ));
 
             let arguments = method_call.arguments.into_iter();
             let arguments = arguments.map(|argument| Value::expression(argument.kind)).collect();
-            let arguments =
-                Value::Slice(arguments, Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))));
+            let arguments = Shared::new(Value::Slice(
+                arguments,
+                Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))),
+            ));
 
             Some(Value::Tuple(vec![object, name, generics, arguments]))
         } else {
@@ -2131,8 +2153,8 @@ fn expr_as_repeated_element_array(
         ))) = expr
         {
             Some(Value::Tuple(vec![
-                Value::expression(repeated_element.kind),
-                Value::expression(length.kind),
+                Shared::new(Value::expression(repeated_element.kind)),
+                Shared::new(Value::expression(length.kind)),
             ]))
         } else {
             None
@@ -2153,8 +2175,8 @@ fn expr_as_repeated_element_slice(
         ))) = expr
         {
             Some(Value::Tuple(vec![
-                Value::expression(repeated_element.kind),
-                Value::expression(length.kind),
+                Shared::new(Value::expression(repeated_element.kind)),
+                Shared::new(Value::expression(length.kind)),
             ]))
         } else {
             None
@@ -2219,8 +2241,8 @@ fn expr_as_unary_op(
 
             tuple_types.pop().unwrap();
             let unary_op_type = tuple_types.pop().unwrap();
-            let unary_op = new_unary_op(prefix_expr.operator, unary_op_type)?;
-            let rhs = Value::expression(prefix_expr.rhs.kind);
+            let unary_op = Shared::new(new_unary_op(prefix_expr.operator, unary_op_type)?);
+            let rhs = Shared::new(Value::expression(prefix_expr.rhs.kind));
             Some(Value::Tuple(vec![unary_op, rhs]))
         } else {
             None
@@ -2313,16 +2335,16 @@ fn expr_resolve(
     };
 
     let is_some = fields.get(&Rc::new("_is_some".to_string())).unwrap();
-    let Value::Bool(is_some) = is_some else {
+    let Value::Bool(is_some) = is_some.borrow().clone() else {
         panic!("Expected is_some to be a boolean");
     };
 
-    let function_to_resolve_in = if *is_some {
+    let function_to_resolve_in = if is_some {
         let value = fields.get(&Rc::new("_value".to_string())).unwrap();
-        let Value::FunctionDefinition(func_id) = value else {
+        let Value::FunctionDefinition(func_id) = value.borrow().clone() else {
             panic!("Expected option value to be a FunctionDefinition");
         };
-        Some(*func_id)
+        Some(func_id)
     } else {
         interpreter.current_function
     };
@@ -2599,8 +2621,8 @@ fn function_def_parameters(
         .map(|(hir_pattern, typ, _visibility)| {
             let tokens = hir_pattern_to_tokens(interner, hir_pattern);
             let tokens = vecmap(tokens, |token| LocatedToken::new(token, location));
-            let name = Value::Quoted(Rc::new(tokens));
-            let typ = Value::Type(typ.clone());
+            let name = Shared::new(Value::Quoted(Rc::new(tokens)));
+            let typ = Shared::new(Value::Type(typ.clone()));
             Value::Tuple(vec![name, typ])
         })
         .collect();
@@ -2692,10 +2714,11 @@ fn function_def_set_parameters(
             interpreter.elaborator.interner,
             (input_parameter, parameters_argument_location),
         )?;
-        let parameter_type = get_type((tuple.pop().unwrap(), parameters_argument_location))?;
+        let parameter = tuple.pop().unwrap().unwrap_or_clone();
+        let parameter_type = get_type((parameter, parameters_argument_location))?;
         let parameter_pattern = parse(
             interpreter.elaborator,
-            (tuple.pop().unwrap(), parameters_argument_location),
+            (tuple.pop().unwrap().unwrap_or_clone(), parameters_argument_location),
             Parser::parse_pattern_or_error,
             "a pattern",
         )?;
@@ -3030,8 +3053,8 @@ pub(crate) fn option(option_type: Type, value: Option<Value>, location: Location
     };
 
     let mut fields = HashMap::default();
-    fields.insert(Rc::new("_is_some".to_string()), is_some);
-    fields.insert(Rc::new("_value".to_string()), value);
+    fields.insert(Rc::new("_is_some".to_string()), Shared::new(is_some));
+    fields.insert(Rc::new("_value".to_string()), Shared::new(value));
     Value::Struct(fields, option_type)
 }
 
@@ -3098,12 +3121,12 @@ fn derive_generators(
         let y = FieldElement::from_be_bytes_reduce(&y_big.to_bytes_be());
         let mut embedded_curve_point_fields = HashMap::default();
         embedded_curve_point_fields
-            .insert(x_field_name.clone(), Value::Field(SignedField::positive(x)));
+            .insert(x_field_name.clone(), Shared::new(Value::Field(SignedField::positive(x))));
         embedded_curve_point_fields
-            .insert(y_field_name.clone(), Value::Field(SignedField::positive(y)));
+            .insert(y_field_name.clone(), Shared::new(Value::Field(SignedField::positive(y))));
         embedded_curve_point_fields.insert(
             is_infinite_field_name.clone(),
-            Value::Field(SignedField::positive(is_infinite)),
+            Shared::new(Value::Field(SignedField::positive(is_infinite))),
         );
         let embedded_curve_point_struct =
             Value::Struct(embedded_curve_point_fields, *elements.clone());
diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs
index b151c099c46..369348e355b 100644
--- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs
@@ -7,6 +7,7 @@ use noirc_errors::Location;
 use crate::ast::{BinaryOp, ItemVisibility, UnaryOp};
 use crate::elaborator::Elaborator;
 use crate::hir::comptime::display::tokens_to_string;
+use crate::hir::comptime::value::StructFields;
 use crate::hir::comptime::value::unwrap_rc;
 use crate::hir::def_collector::dc_crate::CompilationError;
 use crate::lexer::Lexer;
@@ -108,7 +109,7 @@ pub(crate) fn get_array(
 pub(crate) fn get_struct_fields(
     name: &str,
     (value, location): (Value, Location),
-) -> IResult<(HashMap<Rc<String>, Value>, Type)> {
+) -> IResult<(StructFields, Type)> {
     match value {
         Value::Struct(fields, typ) => Ok((fields, typ)),
         _ => {
@@ -128,7 +129,7 @@ pub(crate) fn get_struct_fields(
 /// Get a specific field of a struct and apply a decoder function on it.
 pub(crate) fn get_struct_field<T>(
     field_name: &str,
-    struct_fields: &HashMap<Rc<String>, Value>,
+    struct_fields: &HashMap<Rc<String>, Shared<Value>>,
     struct_type: &Type,
     location: Location,
     f: impl Fn((Value, Location)) -> IResult<T>,
@@ -141,7 +142,7 @@ pub(crate) fn get_struct_field<T>(
             location,
         });
     };
-    f((value.clone(), location))
+    f((value.borrow().clone(), location))
 }
 
 pub(crate) fn get_bool((value, location): (Value, Location)) -> IResult<bool> {
@@ -231,7 +232,7 @@ pub(crate) fn get_ctstring((value, location): (Value, Location)) -> IResult<Rc<S
 pub(crate) fn get_tuple(
     interner: &NodeInterner,
     (value, location): (Value, Location),
-) -> IResult<Vec<Value>> {
+) -> IResult<Vec<Shared<Value>>> {
     match value {
         Value::Tuple(values) => Ok(values),
         value => {
@@ -681,7 +682,8 @@ pub(crate) fn to_struct(
     fields: impl IntoIterator<Item = (&'static str, Value)>,
     typ: Type,
 ) -> Value {
-    let fields = fields.into_iter().map(|(k, v)| (Rc::new(k.to_string()), v)).collect();
+    let fields =
+        fields.into_iter().map(|(k, v)| (Rc::new(k.to_string()), Shared::new(v))).collect();
     Value::Struct(fields, typ)
 }
 
@@ -699,7 +701,10 @@ pub(crate) fn new_unary_op(operator: UnaryOp, typ: Type) -> Option<Value> {
     };
 
     let mut fields = HashMap::default();
-    fields.insert(Rc::new("op".to_string()), Value::Field(SignedField::positive(unary_op_value)));
+    fields.insert(
+        Rc::new("op".to_string()),
+        Shared::new(Value::Field(SignedField::positive(unary_op_value))),
+    );
 
     Some(Value::Struct(fields, typ))
 }
@@ -709,7 +714,10 @@ pub(crate) fn new_binary_op(operator: BinaryOp, typ: Type) -> Value {
     let binary_op_value = operator.contents as u128;
 
     let mut fields = HashMap::default();
-    fields.insert(Rc::new("op".to_string()), Value::Field(SignedField::positive(binary_op_value)));
+    fields.insert(
+        Rc::new("op".to_string()),
+        Shared::new(Value::Field(SignedField::positive(binary_op_value))),
+    );
 
     Value::Struct(fields, typ)
 }
diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs
index fd777a88d87..f9e8c1192de 100644
--- a/compiler/noirc_frontend/src/hir/comptime/value.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/value.rs
@@ -59,8 +59,14 @@ pub enum Value {
     // in case they use functions such as `Quoted::as_type` which require them.
     Closure(Box<Closure>),
 
-    Tuple(Vec<Value>),
-    Struct(HashMap<Rc<String>, Value>, Type),
+    /// Tuple elements are automatically shared to support projection into a tuple:
+    /// `let elem = &mut tuple.0` should mutate the original element.
+    Tuple(Vec<Shared<Value>>),
+
+    /// Struct elements are automatically shared to support projection:
+    /// `let elem = &mut my_struct.field` should mutate the original element.
+    Struct(StructFields, Type),
+
     Enum(/*tag*/ usize, /*args*/ Vec<Value>, Type),
     Pointer(Shared<Value>, /* auto_deref */ bool, /* mutable */ bool),
     Array(Vector<Value>, Type),
@@ -79,6 +85,8 @@ pub enum Value {
     UnresolvedType(UnresolvedTypeData),
 }
 
+pub type StructFields = HashMap<Rc<String>, Shared<Value>>;
+
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Closure {
     pub lambda: HirLambda,
@@ -145,7 +153,7 @@ impl Value {
             Value::Function(_, typ, _) => return Cow::Borrowed(typ),
             Value::Closure(closure) => return Cow::Borrowed(&closure.typ),
             Value::Tuple(fields) => {
-                Type::Tuple(vecmap(fields, |field| field.get_type().into_owned()))
+                Type::Tuple(vecmap(fields, |field| field.borrow().get_type().into_owned()))
             }
             Value::Struct(_, typ) => return Cow::Borrowed(typ),
             Value::Enum(_, _, typ) => return Cow::Borrowed(typ),
@@ -244,13 +252,14 @@ impl Value {
                 ExpressionKind::Resolved(expr_id)
             }
             Value::Tuple(fields) => {
-                let fields =
-                    try_vecmap(fields, |field| field.into_expression(elaborator, location))?;
+                let fields = try_vecmap(fields, |field| {
+                    field.unwrap_or_clone().into_expression(elaborator, location)
+                })?;
                 ExpressionKind::Tuple(fields)
             }
             Value::Struct(fields, typ) => {
                 let fields = try_vecmap(fields, |(name, field)| {
-                    let field = field.into_expression(elaborator, location)?;
+                    let field = field.unwrap_or_clone().into_expression(elaborator, location)?;
                     Ok((Ident::new(unwrap_rc(name), location), field))
                 })?;
 
@@ -412,13 +421,14 @@ impl Value {
                 return Ok(expr_id);
             }
             Value::Tuple(fields) => {
-                let fields =
-                    try_vecmap(fields, |field| field.into_hir_expression(interner, location))?;
+                let fields = try_vecmap(fields, |field| {
+                    field.unwrap_or_clone().into_hir_expression(interner, location)
+                })?;
                 HirExpression::Tuple(fields)
             }
             Value::Struct(fields, typ) => {
                 let fields = try_vecmap(fields, |(name, field)| {
-                    let field = field.into_hir_expression(interner, location)?;
+                    let field = field.unwrap_or_clone().into_hir_expression(interner, location)?;
                     Ok((Ident::new(unwrap_rc(name), location), field))
                 })?;
 
@@ -615,9 +625,11 @@ impl Value {
             Value::Slice(values, _) => {
                 values.iter().any(|value| value.contains_function_or_closure())
             }
-            Value::Tuple(values) => values.iter().any(|value| value.contains_function_or_closure()),
+            Value::Tuple(values) => {
+                values.iter().any(|value| value.borrow().contains_function_or_closure())
+            }
             Value::Struct(fields, _) => {
-                fields.values().any(|value| value.contains_function_or_closure())
+                fields.values().any(|value| value.borrow().contains_function_or_closure())
             }
             Value::Enum(_, values, _) => {
                 values.iter().any(|value| value.contains_function_or_closure())
@@ -707,6 +719,22 @@ impl Value {
             _ => false, // Unsigned or Field types are never negative
         }
     }
+
+    /// Copies an argument ensuring any references to struct/tuple fields are replaced with fresh ones.
+    pub(crate) fn copy(self) -> Value {
+        match self {
+            Value::Tuple(fields) => {
+                Value::Tuple(vecmap(fields, |field| Shared::new(field.unwrap_or_clone().copy())))
+            }
+            Value::Struct(fields, typ) => {
+                let fields = fields
+                    .into_iter()
+                    .map(|(name, field)| (name, Shared::new(field.unwrap_or_clone().copy())));
+                Value::Struct(fields.collect(), typ)
+            }
+            other => other,
+        }
+    }
 }
 
 /// Unwraps an Rc value without cloning the inner value if the reference count is 1. Clones otherwise.
diff --git a/tooling/lsp/src/requests/hover/from_reference.rs b/tooling/lsp/src/requests/hover/from_reference.rs
index 23a6098b940..cfccd72a9be 100644
--- a/tooling/lsp/src/requests/hover/from_reference.rs
+++ b/tooling/lsp/src/requests/hover/from_reference.rs
@@ -905,7 +905,7 @@ fn append_value_to_string(value: &Value, string: &mut String) -> Option<()> {
                 if index > 0 {
                     string.push_str(", ");
                 }
-                append_value_to_string(value, string)?;
+                append_value_to_string(&value.borrow(), string)?;
             }
             if len == 1 {
                 string.push(',');
diff --git a/tooling/nargo_expand/src/printer.rs b/tooling/nargo_expand/src/printer.rs
index 4cb4ce2957e..ad0a2d33085 100644
--- a/tooling/nargo_expand/src/printer.rs
+++ b/tooling/nargo_expand/src/printer.rs
@@ -849,7 +849,7 @@ impl<'context, 'string> ItemPrinter<'context, 'string> {
                     if index != 0 {
                         self.push_str(", ");
                     }
-                    self.show_value(value);
+                    self.show_value(&value.borrow());
                 }
                 if values.len() == 1 {
                     self.push(',');
@@ -868,7 +868,7 @@ impl<'context, 'string> ItemPrinter<'context, 'string> {
                         self.write_indent();
                         self.push_str(name);
                         self.push_str(": ");
-                        self.show_value(value);
+                        self.show_value(&value.borrow());
                         self.push_str(",\n");
                     }
                     self.decrease_indent();


Test patch:
diff --git a/test_programs/compile_failure/noirc_frontend_tests_struct_numeric_generic_in_struct/src/main.nr b/test_programs/compile_failure/noirc_frontend_tests_struct_numeric_generic_in_struct/src/main.nr
index d1ff69ced46..6327c6ad949 100644
--- a/test_programs/compile_failure/noirc_frontend_tests_struct_numeric_generic_in_struct/src/main.nr
+++ b/test_programs/compile_failure/noirc_frontend_tests_struct_numeric_generic_in_struct/src/main.nr
@@ -1,7 +1,5 @@
+pub struct Foo {
+    inner: u64,
+}
 
-    pub struct Foo {
-        inner: u64
-    }
-
-    pub struct Bar<let N: Foo> { }
-    
\ No newline at end of file
+pub struct Bar<let N: Foo> {}
diff --git a/test_programs/execution_success/struct_assignment_with_shared_ref_to_field/Nargo.toml b/test_programs/execution_success/struct_assignment_with_shared_ref_to_field/Nargo.toml
new file mode 100644
index 00000000000..201866c02ed
--- /dev/null
+++ b/test_programs/execution_success/struct_assignment_with_shared_ref_to_field/Nargo.toml
@@ -0,0 +1,4 @@
+[package]
+name = "struct_assignment_with_shared_ref_to_field"
+type = "bin"
+authors = [""]
diff --git a/test_programs/execution_success/struct_assignment_with_shared_ref_to_field/src/main.nr b/test_programs/execution_success/struct_assignment_with_shared_ref_to_field/src/main.nr
new file mode 100644
index 00000000000..2c6ad52155d
--- /dev/null
+++ b/test_programs/execution_success/struct_assignment_with_shared_ref_to_field/src/main.nr
@@ -0,0 +1,24 @@
+fn main() {
+    let (a, b) = comptime { (plain_store(), store_through_pointer()) };
+    let (c, d) = (plain_store(), store_through_pointer());
+    assert(a);
+    assert(b);
+    assert(c);
+
+    // Bug! https://github.com/noir-lang/noir/issues/9251
+    assert(!d);
+}
+
+fn plain_store() -> bool {
+    let mut a = (false,);
+    let b = &mut a.0;
+    a = (true,);
+    *b
+}
+
+fn store_through_pointer() -> bool {
+    let a = &mut (false,);
+    let b = &mut a.0;
+    *a = (true,);
+    *b
+}
diff --git a/tooling/nargo_cli/tests/snapshots/compile_failure/noirc_frontend_tests_struct_numeric_generic_in_struct/execute__tests__stderr.snap b/tooling/nargo_cli/tests/snapshots/compile_failure/noirc_frontend_tests_struct_numeric_generic_in_struct/execute__tests__stderr.snap
index 0dad40641bb..6da7f11db2f 100644
--- a/tooling/nargo_cli/tests/snapshots/compile_failure/noirc_frontend_tests_struct_numeric_generic_in_struct/execute__tests__stderr.snap
+++ b/tooling/nargo_cli/tests/snapshots/compile_failure/noirc_frontend_tests_struct_numeric_generic_in_struct/execute__tests__stderr.snap
@@ -3,10 +3,10 @@ source: tooling/nargo_cli/tests/execute.rs
 expression: stderr
 ---
 error: N has a type of Foo. The only supported numeric generic types are `u1`, `u8`, `u16`, and `u32`.
-  ┌─ src/main.nr:6:27
+  ┌─ src/main.nr:5:23
   │
-6 │     pub struct Bar<let N: Foo> { }
-  │                           --- Unsupported numeric generic type
+5 │ pub struct Bar<let N: Foo> {}
+  │                       --- Unsupported numeric generic type
   │
 
 Aborting due to 1 previous error
diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__expanded.snap b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__expanded.snap
new file mode 100644
index 00000000000..54da29923f7
--- /dev/null
+++ b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__expanded.snap
@@ -0,0 +1,26 @@
+---
+source: tooling/nargo_cli/tests/execute.rs
+expression: expanded_code
+---
+fn main() {
+    let (a, b): (bool, bool) = (true, true);
+    let (c, d): (bool, bool) = (plain_store(), store_through_pointer());
+    assert(a);
+    assert(b);
+    assert(c);
+    assert(!d);
+}
+
+fn plain_store() -> bool {
+    let mut a: (bool,) = (false,);
+    let b: &mut bool = &mut a.0;
+    a = (true,);
+    *b
+}
+
+fn store_through_pointer() -> bool {
+    let a: &mut (bool,) = &mut (false,);
+    let b: &mut bool = &mut a.0;
+    *(a) = (true,);
+    *b
+}
diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_false_inliner_-9223372036854775808.snap b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_false_inliner_-9223372036854775808.snap
new file mode 100644
index 00000000000..7de940827c8
--- /dev/null
+++ b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_false_inliner_-9223372036854775808.snap
@@ -0,0 +1,26 @@
+---
+source: tooling/nargo_cli/tests/execute.rs
+expression: artifact
+---
+{
+  "noir_version": "[noir_version]",
+  "hash": "[hash]",
+  "abi": {
+    "parameters": [],
+    "return_type": null,
+    "error_types": {}
+  },
+  "bytecode": [
+    "func 0",
+    "current witness index : _0",
+    "private parameters indices : []",
+    "public parameters indices : []",
+    "return value indices : []"
+  ],
+  "debug_symbols": "XY5BCsQwCEXv4rqLWfcqw1BsaosgJtikMITefWyYQOlK/3/6tcJCc9km1jXuML4rzMYivE0SA2aO6m49B+hyykbkFty4byU00gyjFpEBDpTShvaE2mpGc/oagHTx6oErC13d+XGBge158UBjnIX+ci0abjR/Uyf942Qx0FKMrqTGPPsH",
+  "file_map": {},
+  "names": [
+    "main"
+  ],
+  "brillig_names": []
+}
diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_false_inliner_0.snap b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_false_inliner_0.snap
new file mode 100644
index 00000000000..7de940827c8
--- /dev/null
+++ b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_false_inliner_0.snap
@@ -0,0 +1,26 @@
+---
+source: tooling/nargo_cli/tests/execute.rs
+expression: artifact
+---
+{
+  "noir_version": "[noir_version]",
+  "hash": "[hash]",
+  "abi": {
+    "parameters": [],
+    "return_type": null,
+    "error_types": {}
+  },
+  "bytecode": [
+    "func 0",
+    "current witness index : _0",
+    "private parameters indices : []",
+    "public parameters indices : []",
+    "return value indices : []"
+  ],
+  "debug_symbols": "XY5BCsQwCEXv4rqLWfcqw1BsaosgJtikMITefWyYQOlK/3/6tcJCc9km1jXuML4rzMYivE0SA2aO6m49B+hyykbkFty4byU00gyjFpEBDpTShvaE2mpGc/oagHTx6oErC13d+XGBge158UBjnIX+ci0abjR/Uyf942Qx0FKMrqTGPPsH",
+  "file_map": {},
+  "names": [
+    "main"
+  ],
+  "brillig_names": []
+}
diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_false_inliner_9223372036854775807.snap b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_false_inliner_9223372036854775807.snap
new file mode 100644
index 00000000000..7de940827c8
--- /dev/null
+++ b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_false_inliner_9223372036854775807.snap
@@ -0,0 +1,26 @@
+---
+source: tooling/nargo_cli/tests/execute.rs
+expression: artifact
+---
+{
+  "noir_version": "[noir_version]",
+  "hash": "[hash]",
+  "abi": {
+    "parameters": [],
+    "return_type": null,
+    "error_types": {}
+  },
+  "bytecode": [
+    "func 0",
+    "current witness index : _0",
+    "private parameters indices : []",
+    "public parameters indices : []",
+    "return value indices : []"
+  ],
+  "debug_symbols": "XY5BCsQwCEXv4rqLWfcqw1BsaosgJtikMITefWyYQOlK/3/6tcJCc9km1jXuML4rzMYivE0SA2aO6m49B+hyykbkFty4byU00gyjFpEBDpTShvaE2mpGc/oagHTx6oErC13d+XGBge158UBjnIX+ci0abjR/Uyf942Qx0FKMrqTGPPsH",
+  "file_map": {},
+  "names": [
+    "main"
+  ],
+  "brillig_names": []
+}
diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_true_inliner_-9223372036854775808.snap b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_true_inliner_-9223372036854775808.snap
new file mode 100644
index 00000000000..41e0c42011c
--- /dev/null
+++ b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_true_inliner_-9223372036854775808.snap
@@ -0,0 +1,36 @@
+---
+source: tooling/nargo_cli/tests/execute.rs
+expression: artifact
+---
+{
+  "noir_version": "[noir_version]",
+  "hash": "[hash]",
+  "abi": {
+    "parameters": [],
+    "return_type": null,
+    "error_types": {
+      "17843811134343075018": {
+        "error_kind": "string",
+        "string": "Stack too deep"
+      }
+    }
+  },
+  "bytecode": [
+    "func 0",
+    "current witness index : _0",
+    "private parameters indices : []",
+    "public parameters indices : []",
+    "return value indices : []",
+    "BRILLIG CALL func 0: inputs: [], outputs: []",
+    "unconstrained func 0",
+    "[Const { destination: Direct(2), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(1), bit_size: Integer(U32), value: 32836 }, Const { destination: Direct(0), bit_size: Integer(U32), value: 3 }, Const { destination: Relative(1), bit_size: Integer(U32), value: 0 }, Const { destination: Relative(2), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(32836), size_address: Relative(1), offset_address: Relative(2) }, Call { location: 11 }, Call { location: 12 }, Const { destination: Relative(1), bit_size: Integer(U32), value: 32836 }, Const { destination: Relative(2), bit_size: Integer(U32), value: 0 }, Stop { return_data: HeapVector { pointer: Relative(1), size: Relative(2) } }, Return, Call { location: 14 }, Return, Const { destination: Direct(32772), bit_size: Integer(U32), value: 30720 }, BinaryIntOp { destination: Direct(32771), op: LessThan, bit_size: U32, lhs: Direct(0), rhs: Direct(32772) }, JumpIf { condition: Direct(32771), location: 19 }, IndirectConst { destination_pointer: Direct(1), bit_size: Integer(U64), value: 17843811134343075018 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Direct(2) } }, Return]"
+  ],
+  "debug_symbols": "XY7BCoRACIbfxfMcCvayvUpE2GQxIM5gM8ESvfs6sUHsRf391N8DZprKOgZZ4gZdf8CkgTmsI0ePOUSx7nE6uOWYlcha8OC2lVBJMnRSmB3syOUa2hLKlTOq0cYByWzZDi6BqVbnYAJ90H/HHTXgxPSTSxH/oPmTbnJ/nDR6motSvVQZNDW0Fvv25dr3cFa3Lw==",
+  "file_map": {},
+  "names": [
+    "main"
+  ],
+  "brillig_names": [
+    "main"
+  ]
+}
diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_true_inliner_0.snap b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_true_inliner_0.snap
new file mode 100644
index 00000000000..41e0c42011c
--- /dev/null
+++ b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_true_inliner_0.snap
@@ -0,0 +1,36 @@
+---
+source: tooling/nargo_cli/tests/execute.rs
+expression: artifact
+---
+{
+  "noir_version": "[noir_version]",
+  "hash": "[hash]",
+  "abi": {
+    "parameters": [],
+    "return_type": null,
+    "error_types": {
+      "17843811134343075018": {
+        "error_kind": "string",
+        "string": "Stack too deep"
+      }
+    }
+  },
+  "bytecode": [
+    "func 0",
+    "current witness index : _0",
+    "private parameters indices : []",
+    "public parameters indices : []",
+    "return value indices : []",
+    "BRILLIG CALL func 0: inputs: [], outputs: []",
+    "unconstrained func 0",
+    "[Const { destination: Direct(2), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(1), bit_size: Integer(U32), value: 32836 }, Const { destination: Direct(0), bit_size: Integer(U32), value: 3 }, Const { destination: Relative(1), bit_size: Integer(U32), value: 0 }, Const { destination: Relative(2), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(32836), size_address: Relative(1), offset_address: Relative(2) }, Call { location: 11 }, Call { location: 12 }, Const { destination: Relative(1), bit_size: Integer(U32), value: 32836 }, Const { destination: Relative(2), bit_size: Integer(U32), value: 0 }, Stop { return_data: HeapVector { pointer: Relative(1), size: Relative(2) } }, Return, Call { location: 14 }, Return, Const { destination: Direct(32772), bit_size: Integer(U32), value: 30720 }, BinaryIntOp { destination: Direct(32771), op: LessThan, bit_size: U32, lhs: Direct(0), rhs: Direct(32772) }, JumpIf { condition: Direct(32771), location: 19 }, IndirectConst { destination_pointer: Direct(1), bit_size: Integer(U64), value: 17843811134343075018 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Direct(2) } }, Return]"
+  ],
+  "debug_symbols": "XY7BCoRACIbfxfMcCvayvUpE2GQxIM5gM8ESvfs6sUHsRf391N8DZprKOgZZ4gZdf8CkgTmsI0ePOUSx7nE6uOWYlcha8OC2lVBJMnRSmB3syOUa2hLKlTOq0cYByWzZDi6BqVbnYAJ90H/HHTXgxPSTSxH/oPmTbnJ/nDR6motSvVQZNDW0Fvv25dr3cFa3Lw==",
+  "file_map": {},
+  "names": [
+    "main"
+  ],
+  "brillig_names": [
+    "main"
+  ]
+}
diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_true_inliner_9223372036854775807.snap b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_true_inliner_9223372036854775807.snap
new file mode 100644
index 00000000000..41e0c42011c
--- /dev/null
+++ b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__force_brillig_true_inliner_9223372036854775807.snap
@@ -0,0 +1,36 @@
+---
+source: tooling/nargo_cli/tests/execute.rs
+expression: artifact
+---
+{
+  "noir_version": "[noir_version]",
+  "hash": "[hash]",
+  "abi": {
+    "parameters": [],
+    "return_type": null,
+    "error_types": {
+      "17843811134343075018": {
+        "error_kind": "string",
+        "string": "Stack too deep"
+      }
+    }
+  },
+  "bytecode": [
+    "func 0",
+    "current witness index : _0",
+    "private parameters indices : []",
+    "public parameters indices : []",
+    "return value indices : []",
+    "BRILLIG CALL func 0: inputs: [], outputs: []",
+    "unconstrained func 0",
+    "[Const { destination: Direct(2), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(1), bit_size: Integer(U32), value: 32836 }, Const { destination: Direct(0), bit_size: Integer(U32), value: 3 }, Const { destination: Relative(1), bit_size: Integer(U32), value: 0 }, Const { destination: Relative(2), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(32836), size_address: Relative(1), offset_address: Relative(2) }, Call { location: 11 }, Call { location: 12 }, Const { destination: Relative(1), bit_size: Integer(U32), value: 32836 }, Const { destination: Relative(2), bit_size: Integer(U32), value: 0 }, Stop { return_data: HeapVector { pointer: Relative(1), size: Relative(2) } }, Return, Call { location: 14 }, Return, Const { destination: Direct(32772), bit_size: Integer(U32), value: 30720 }, BinaryIntOp { destination: Direct(32771), op: LessThan, bit_size: U32, lhs: Direct(0), rhs: Direct(32772) }, JumpIf { condition: Direct(32771), location: 19 }, IndirectConst { destination_pointer: Direct(1), bit_size: Integer(U64), value: 17843811134343075018 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Direct(2) } }, Return]"
+  ],
+  "debug_symbols": "XY7BCoRACIbfxfMcCvayvUpE2GQxIM5gM8ESvfs6sUHsRf391N8DZprKOgZZ4gZdf8CkgTmsI0ePOUSx7nE6uOWYlcha8OC2lVBJMnRSmB3syOUa2hLKlTOq0cYByWzZDi6BqVbnYAJ90H/HHTXgxPSTSxH/oPmTbnJ/nDR6motSvVQZNDW0Fvv25dr3cFa3Lw==",
+  "file_map": {},
+  "names": [
+    "main"
+  ],
+  "brillig_names": [
+    "main"
+  ]
+}
diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__stdout.snap b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__stdout.snap
new file mode 100644
index 00000000000..e86e3de90e1
--- /dev/null
+++ b/tooling/nargo_cli/tests/snapshots/execution_success/struct_assignment_with_shared_ref_to_field/execute__tests__stdout.snap
@@ -0,0 +1,5 @@
+---
+source: tooling/nargo_cli/tests/execute.rs
+expression: stdout
+---
+