diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index ad79af4e63d..d2e3e153518 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -440,8 +440,13 @@ impl<'context> Elaborator<'context> { let mut unseen_fields = struct_type.borrow().field_names(); for (field_name, field) in fields { - let expected_type = field_types.iter().find(|(name, _)| name == &field_name.0.contents); - let expected_type = expected_type.map(|(_, typ)| typ).unwrap_or(&Type::Error); + let expected_field_with_index = field_types + .iter() + .enumerate() + .find(|(_, (name, _))| name == &field_name.0.contents); + let expected_index = expected_field_with_index.map(|(index, _)| index); + let expected_type = + expected_field_with_index.map(|(_, (_, typ))| typ).unwrap_or(&Type::Error); let field_span = field.span; let (resolved, field_type) = self.elaborate_expression(field); @@ -468,6 +473,14 @@ impl<'context> Elaborator<'context> { }); } + if let Some(expected_index) = expected_index { + let struct_id = struct_type.borrow().id; + let referenced = ReferenceId::StructMember(struct_id, expected_index); + let reference = + ReferenceId::Reference(Location::new(field_name.span(), self.file), false); + self.interner.add_reference(referenced, reference); + } + ret.push((field_name, resolved)); } @@ -489,10 +502,11 @@ impl<'context> Elaborator<'context> { ) -> (ExprId, Type) { let (lhs, lhs_type) = self.elaborate_expression(access.lhs); let rhs = access.rhs; + let rhs_span = rhs.span(); // `is_offset` is only used when lhs is a reference and we want to return a reference to rhs let access = HirMemberAccess { lhs, rhs, is_offset: false }; let expr_id = self.intern_expr(HirExpression::MemberAccess(access.clone()), span); - let typ = self.type_check_member_access(access, expr_id, lhs_type, span); + let typ = self.type_check_member_access(access, expr_id, lhs_type, rhs_span); self.interner.push_expr_type(expr_id, typ.clone()); (expr_id, typ) } diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 6104da582a7..f9ae2570b93 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1191,6 +1191,7 @@ impl<'context> Elaborator<'context> { let span = typ.struct_def.span; let fields = self.resolve_struct_fields(typ.struct_def, type_id); + let fields_len = fields.len(); self.interner.update_struct(type_id, |struct_def| { struct_def.set_fields(fields); @@ -1217,6 +1218,11 @@ impl<'context> Elaborator<'context> { } }); + for field_index in 0..fields_len { + self.interner + .add_definition_location(ReferenceId::StructMember(type_id, field_index)); + } + self.run_comptime_attributes_on_struct(attributes, type_id, span, &mut generated_items); } diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index b1c9b1b37cc..0e4822d76e3 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -202,10 +202,18 @@ impl<'context> Elaborator<'context> { new_definitions, ); - let referenced = ReferenceId::Struct(struct_type.borrow().id); + let struct_id = struct_type.borrow().id; + + let referenced = ReferenceId::Struct(struct_id); let reference = ReferenceId::Reference(Location::new(name_span, self.file), is_self_type); self.interner.add_reference(referenced, reference); + for (field_index, field) in fields.iter().enumerate() { + let referenced = ReferenceId::StructMember(struct_id, field_index); + let reference = ReferenceId::Reference(Location::new(field.0.span(), self.file), false); + self.interner.add_reference(referenced, reference); + } + HirPattern::Struct(expected_type, fields, location) } diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index 1fc92ad28ba..9410324496d 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -381,6 +381,10 @@ impl<'context> Elaborator<'context> { Type::Struct(s, args) => { let s = s.borrow(); if let Some((field, index)) = s.get_field(field_name, args) { + let referenced = ReferenceId::StructMember(s.id, index); + let reference = ReferenceId::Reference(Location::new(span, self.file), false); + self.interner.add_reference(referenced, reference); + return Some((field, index)); } } diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 6777c0d1c79..8183911c845 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -342,6 +342,11 @@ impl StructType { vecmap(&self.fields, |(name, typ)| (name.0.contents.clone(), typ.clone())) } + /// Returns the field at the given index. Panics if no field exists at the given index. + pub fn field_at(&self, index: usize) -> &(Ident, Type) { + &self.fields[index] + } + pub fn field_names(&self) -> BTreeSet { self.fields.iter().map(|(name, _)| name.clone()).collect() } diff --git a/compiler/noirc_frontend/src/locations.rs b/compiler/noirc_frontend/src/locations.rs index fcaef0a8dd6..0efe385aa0a 100644 --- a/compiler/noirc_frontend/src/locations.rs +++ b/compiler/noirc_frontend/src/locations.rs @@ -38,6 +38,11 @@ impl NodeInterner { let struct_type = struct_type.borrow(); Location::new(struct_type.name.span(), struct_type.location.file) } + ReferenceId::StructMember(id, field_index) => { + let struct_type = self.get_struct(id); + let struct_type = struct_type.borrow(); + Location::new(struct_type.field_at(field_index).0.span(), struct_type.location.file) + } ReferenceId::Trait(id) => { let trait_type = self.get_trait(id); Location::new(trait_type.name.span(), trait_type.location.file) diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 588b56afa1a..2de9b04f591 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -241,6 +241,7 @@ pub enum DependencyId { pub enum ReferenceId { Module(ModuleId), Struct(StructId), + StructMember(StructId, usize), Trait(TraitId), Global(GlobalId), Function(FuncId), diff --git a/tooling/lsp/src/requests/rename.rs b/tooling/lsp/src/requests/rename.rs index ac6c6792e15..906a5cbcaab 100644 --- a/tooling/lsp/src/requests/rename.rs +++ b/tooling/lsp/src/requests/rename.rs @@ -199,4 +199,9 @@ mod rename_tests { async fn test_rename_local_variable() { check_rename_succeeds("local_variable", "some_var").await; } + + #[test] + async fn test_rename_struct_member() { + check_rename_succeeds("struct_member", "some_member").await; + } } diff --git a/tooling/lsp/test_programs/struct_member/Nargo.toml b/tooling/lsp/test_programs/struct_member/Nargo.toml new file mode 100644 index 00000000000..5272b9abb68 --- /dev/null +++ b/tooling/lsp/test_programs/struct_member/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "struct_member" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/lsp/test_programs/struct_member/src/main.nr b/tooling/lsp/test_programs/struct_member/src/main.nr new file mode 100644 index 00000000000..3f1bac9df66 --- /dev/null +++ b/tooling/lsp/test_programs/struct_member/src/main.nr @@ -0,0 +1,12 @@ +struct Foo { + some_member: Field +} + +fn main() { + let mut foo = Foo { some_member: 1 }; + foo.some_member = 2; + let _ = foo.some_member; + + let Foo { some_member } = foo; + let Foo { some_member: some_var } = foo; +}