From 3c731b11b31b8556eeebc4fe59b68609aa96c463 Mon Sep 17 00:00:00 2001 From: jfecher Date: Thu, 7 Sep 2023 12:21:17 -0500 Subject: [PATCH] fix: Implement auto-dereferencing when calling methods (#2581) Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- .../execution_success/references/src/main.nr | 13 ++++++++ .../noirc_frontend/src/hir/type_check/expr.rs | 31 +++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/references/src/main.nr b/crates/nargo_cli/tests/execution_success/references/src/main.nr index 70de5cada3f..be02f2b10d6 100644 --- a/crates/nargo_cli/tests/execution_success/references/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/references/src/main.nr @@ -41,6 +41,8 @@ fn main(mut x: Field) { regression_2218_if_inner_else(20, x); regression_2218_else(x, 3); regression_2218_loop(x, 10); + + regression_2560(s_ref); } fn add1(x: &mut Field) { @@ -64,6 +66,10 @@ impl S { fn add2(&mut self) { self.y += 2; } + + fn get_y(self) -> Field { + self.y + } } fn mutate_copy(mut a: Field) { @@ -230,3 +236,10 @@ fn regression_2218_loop(x: Field, y: Field) { } assert(*q1 == 2); } + +// This is more a feature test than a proper regression. +// Before, we never automatically dereferenced objects in method calls to their value types. +// Now, we insert as many `*` as necessary to get to `S`. +fn regression_2560(s_ref: &mut S) { + assert(s_ref.get_y() == 7); +} diff --git a/crates/noirc_frontend/src/hir/type_check/expr.rs b/crates/noirc_frontend/src/hir/type_check/expr.rs index a150f198281..e4e632835a1 100644 --- a/crates/noirc_frontend/src/hir/type_check/expr.rs +++ b/crates/noirc_frontend/src/hir/type_check/expr.rs @@ -319,9 +319,9 @@ impl<'interner> TypeChecker<'interner> { }; if let Some(expected_object_type) = expected_object_type { - if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) { - let actual_type = argument_types[0].0.follow_bindings(); + let actual_type = argument_types[0].0.follow_bindings(); + if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) { if !matches!(actual_type, Type::MutableReference(_)) { if let Err(error) = verify_mutable_reference(self.interner, method_call.object) { @@ -350,10 +350,37 @@ impl<'interner> TypeChecker<'interner> { ); } } + // Otherwise if the object type is a mutable reference and the method is not, insert as + // many dereferences as needed. + } else if matches!(actual_type, Type::MutableReference(_)) { + let (object, new_type) = + self.insert_auto_dereferences(method_call.object, actual_type); + method_call.object = object; + argument_types[0].0 = new_type; } } } + /// Insert as many dereference operations as necessary to automatically dereference a method + /// call object to its base value type T. + fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) { + if let Type::MutableReference(element) = typ { + let location = self.interner.id_location(object); + + let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { + operator: UnaryOp::Dereference { implicitly_added: true }, + rhs: object, + })); + self.interner.push_expr_type(&object, element.as_ref().clone()); + self.interner.push_expr_location(object, location.span, location.file); + + // Recursively dereference to allow for converting &mut &mut T to T + self.insert_auto_dereferences(object, *element) + } else { + (object, typ) + } + } + /// Given a method object: `(*foo).bar` of a method call `(*foo).bar.baz()`, remove the /// implicitly added dereference operator if one is found. ///