diff --git a/crates/nargo_cli/tests/test_data/tuples/src/main.nr b/crates/nargo_cli/tests/test_data/tuples/src/main.nr index 4a003dc5a42..b1d310b1412 100644 --- a/crates/nargo_cli/tests/test_data/tuples/src/main.nr +++ b/crates/nargo_cli/tests/test_data/tuples/src/main.nr @@ -9,11 +9,21 @@ fn main(x: Field, y: Field) { assert(a == 0); assert(b == 1); - let (u,v) = if x as u32 <1 { - (x,x+1) + let (u,v) = if x as u32 < 1 { + (x, x + 1) } else { - (x+1,x) + (x + 1, x) }; - assert(u==x+1); - assert(v==x); + assert(u == x+1); + assert(v == x); + + // Test mutating tuples + let mut mutable = ((0, 0), 1, 2, 3); + mutable.0 = pair; + mutable.2 = 7; + assert(mutable.0.0 == 1); + assert(mutable.0.1 == 0); + assert(mutable.1 == 1); + assert(mutable.2 == 7); + assert(mutable.3 == 3); } diff --git a/crates/noirc_driver/src/contract.rs b/crates/noirc_driver/src/contract.rs index 3f69a06d1e1..c0a54534941 100644 --- a/crates/noirc_driver/src/contract.rs +++ b/crates/noirc_driver/src/contract.rs @@ -1,7 +1,7 @@ +use crate::program::{deserialize_circuit, serialize_circuit}; use acvm::acir::circuit::Circuit; use noirc_abi::Abi; use serde::{Deserialize, Serialize}; -use crate::program::{serialize_circuit, deserialize_circuit}; /// Describes the types of smart contract functions that are allowed. /// Unlike the similar enum in noirc_frontend, 'open' and 'unconstrained' diff --git a/crates/noirc_frontend/src/hir/type_check/expr.rs b/crates/noirc_frontend/src/hir/type_check/expr.rs index 1929af8d223..8a91ecbfde8 100644 --- a/crates/noirc_frontend/src/hir/type_check/expr.rs +++ b/crates/noirc_frontend/src/hir/type_check/expr.rs @@ -455,18 +455,47 @@ impl<'interner> TypeChecker<'interner> { fn check_member_access(&mut self, access: expr::HirMemberAccess, expr_id: ExprId) -> Type { let lhs_type = self.check_expression(&access.lhs).follow_bindings(); + let span = self.interner.expr_span(&expr_id); + + match self.check_field_access(&lhs_type, &access.rhs.0.contents, span) { + Some((element_type, index)) => { + self.interner.set_field_index(expr_id, index); + element_type + } + None => Type::Error, + } + } + + /// This will verify that an expression in the form `lhs.rhs_name` has the given field and will push + /// a type error if it does not. If there is no error, the type of the struct/tuple field is returned + /// along with the index of the field in question. + /// + /// This function is abstracted from check_member_access so that it can be shared between + /// there and the HirLValue::MemberAccess case of check_lvalue. + pub(super) fn check_field_access( + &mut self, + lhs_type: &Type, + field_name: &str, + span: Span, + ) -> Option<(Type, usize)> { + let lhs_type = lhs_type.follow_bindings(); if let Type::Struct(s, args) = &lhs_type { let s = s.borrow(); - if let Some((field, index)) = s.get_field(&access.rhs.0.contents, args) { - self.interner.set_field_index(expr_id, index); - return field; + if let Some((field, index)) = s.get_field(field_name, args) { + return Some((field, index)); } } else if let Type::Tuple(elements) = &lhs_type { - if let Ok(index) = access.rhs.0.contents.parse::() { - if index < elements.len() { - self.interner.set_field_index(expr_id, index); - return elements[index].clone(); + if let Ok(index) = field_name.parse::() { + let length = elements.len(); + if index < length { + return Some((elements[index].clone(), index)); + } else { + self.errors.push(TypeCheckError::Unstructured { + msg: format!("Index {index} is out of bounds for this tuple {lhs_type} of length {length}"), + span, + }); + return None; } } } @@ -474,17 +503,13 @@ impl<'interner> TypeChecker<'interner> { // If we get here the type has no field named 'access.rhs'. // Now we specialize the error message based on whether we know the object type in question yet. if let Type::TypeVariable(..) = &lhs_type { - self.errors.push(TypeCheckError::TypeAnnotationsNeeded { - span: self.interner.expr_span(&access.lhs), - }); + self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); } else if lhs_type != Type::Error { - self.errors.push(TypeCheckError::Unstructured { - msg: format!("Type {lhs_type} has no member named {}", access.rhs), - span: self.interner.expr_span(&access.lhs), - }); + let msg = format!("Type {lhs_type} has no member named {field_name}"); + self.errors.push(TypeCheckError::Unstructured { msg, span }); } - Type::Error + None } fn comparator_operand_type_rules( diff --git a/crates/noirc_frontend/src/hir/type_check/stmt.rs b/crates/noirc_frontend/src/hir/type_check/stmt.rs index 73ae8ebc94e..7bd50392404 100644 --- a/crates/noirc_frontend/src/hir/type_check/stmt.rs +++ b/crates/noirc_frontend/src/hir/type_check/stmt.rs @@ -142,28 +142,15 @@ impl<'interner> TypeChecker<'interner> { (typ.clone(), HirLValue::Ident(ident, typ)) } HirLValue::MemberAccess { object, field_name, .. } => { - let (result, object) = self.check_lvalue(*object, assign_span); + let (lhs_type, object) = self.check_lvalue(*object, assign_span); let object = Box::new(object); - let mut error = |typ| { - self.errors.push(TypeCheckError::Unstructured { - msg: format!("Type {typ} has no member named {field_name}"), - span: field_name.span(), - }); - (Type::Error, None) - }; - - let (typ, field_index) = match result.follow_bindings() { - Type::Struct(def, args) => { - match def.borrow().get_field(&field_name.0.contents, &args) { - Some((field, index)) => (field, Some(index)), - None => error(Type::Struct(def.clone(), args)), - } - } - Type::Error => (Type::Error, None), - other => error(other), - }; + let span = field_name.span(); + let (typ, field_index) = self + .check_field_access(&lhs_type, &field_name.0.contents, span) + .unwrap_or((Type::Error, 0)); + let field_index = Some(field_index); (typ.clone(), HirLValue::MemberAccess { object, field_name, field_index, typ }) } HirLValue::Index { array, index, .. } => { diff --git a/crates/noirc_frontend/src/parser/parser.rs b/crates/noirc_frontend/src/parser/parser.rs index 4f7c73e609b..d83cf6fd710 100644 --- a/crates/noirc_frontend/src/parser/parser.rs +++ b/crates/noirc_frontend/src/parser/parser.rs @@ -534,7 +534,7 @@ where { let l_ident = ident().map(LValue::Ident); - let l_member_rhs = just(Token::Dot).ignore_then(ident()).map(LValueRhs::MemberAccess); + let l_member_rhs = just(Token::Dot).ignore_then(field_name()).map(LValueRhs::MemberAccess); let l_index = expr_parser .delimited_by(just(Token::LeftBracket), just(Token::RightBracket))