diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 3074a3db335..4bbc9d34d7e 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -4,7 +4,9 @@ use std::{ }; use async_lsp::ResponseError; -use completion_items::{crate_completion_item, simple_completion_item}; +use completion_items::{ + crate_completion_item, simple_completion_item, struct_field_completion_item, +}; use fm::{FileId, PathString}; use kinds::{FunctionCompletionKind, FunctionKind, ModuleCompletionKind, RequestedItems}; use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse}; @@ -472,11 +474,48 @@ impl<'a> NodeFinder<'a> { fn find_in_constructor_expression(&mut self, constructor_expression: &ConstructorExpression) { self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); + // Check if we need to autocomplete the field name + if constructor_expression + .fields + .iter() + .any(|(field_name, _)| field_name.span().end() as usize == self.byte_index) + { + self.complete_constructor_field_name(constructor_expression); + return; + } + for (_field_name, expression) in &constructor_expression.fields { self.find_in_expression(expression); } } + fn complete_constructor_field_name(&mut self, constructor_expression: &ConstructorExpression) { + let location = + Location::new(constructor_expression.type_name.last_ident().span(), self.file); + let Some(ReferenceId::Struct(struct_id)) = self.interner.find_referenced(location) else { + return; + }; + + let struct_type = self.interner.get_struct(struct_id); + let struct_type = struct_type.borrow(); + + // First get all of the struct's fields + let mut fields = HashMap::new(); + let fields_as_written = struct_type.get_fields_as_written(); + for (field, typ) in &fields_as_written { + fields.insert(field, typ); + } + + // Remove the ones that already exists in the constructor + for (field, _) in &constructor_expression.fields { + fields.remove(&field.0.contents); + } + + for (field, typ) in fields { + self.completion_items.push(struct_field_completion_item(field, typ)); + } + } + fn find_in_member_access_expression( &mut self, member_access_expression: &MemberAccessExpression, @@ -905,13 +944,9 @@ impl<'a> NodeFinder<'a> { generics: &[Type], prefix: &str, ) { - for (name, typ) in struct_type.get_fields(generics) { - if name_matches(&name, prefix) { - self.completion_items.push(simple_completion_item( - name, - CompletionItemKind::FIELD, - Some(typ.to_string()), - )); + for (name, typ) in &struct_type.get_fields(generics) { + if name_matches(name, prefix) { + self.completion_items.push(struct_field_completion_item(name, typ)); } } } diff --git a/tooling/lsp/src/requests/completion/completion_items.rs b/tooling/lsp/src/requests/completion/completion_items.rs index f967845e184..29f5de0c1fb 100644 --- a/tooling/lsp/src/requests/completion/completion_items.rs +++ b/tooling/lsp/src/requests/completion/completion_items.rs @@ -298,6 +298,10 @@ fn type_to_self_string(typ: &Type, string: &mut String) { } } +pub(super) fn struct_field_completion_item(field: &str, typ: &Type) -> CompletionItem { + simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.to_string())) +} + pub(super) fn simple_completion_item( label: impl Into, kind: CompletionItemKind, diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index ed8331c3417..c8be889748f 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1390,4 +1390,30 @@ mod completion_tests { ], ); } + + #[test] + async fn test_completes_constructor_fields() { + let src = r#" + mod foobar { + struct Foo { + bb: i32, + bbb: Field, + bbbb: bool, + bbbbb: str<6>, + } + } + + fn main() { + foobar::Foo { bbb: 1, b>|<, bbbbb } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item("bb", CompletionItemKind::FIELD, Some("i32".to_string())), + simple_completion_item("bbbb", CompletionItemKind::FIELD, Some("bool".to_string())), + ], + ) + .await; + } }