From 45b1918bf9429a32b8472aed5d952cc4b6dfb296 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 22 Aug 2024 10:25:15 -0300 Subject: [PATCH 01/10] Allow LSP completion in the middle of paths --- tooling/lsp/src/requests/completion.rs | 115 ++++++++++++++---- .../src/requests/completion/auto_import.rs | 9 +- .../lsp/src/requests/completion/builtins.rs | 27 +++- tooling/lsp/src/requests/completion/tests.rs | 64 ++++++++++ 4 files changed, 186 insertions(+), 29 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 28388230e94..e829a552c3a 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -663,15 +663,55 @@ impl<'a> NodeFinder<'a> { } fn find_in_path(&mut self, path: &Path, requested_items: RequestedItems) { - // Only offer completions if we are right at the end of the path - if self.byte_index != path.span.end() as usize { + if !self.includes_span(path.span) { return; } let after_colons = self.byte == Some(b':'); - let mut idents: Vec = - path.segments.iter().map(|segment| segment.ident.clone()).collect(); + let mut idents: Vec = Vec::new(); + + // Are we in the middle of an ident? + let mut in_the_middle = false; + + // Find in which ident we are in, and in which part of it + // (it could be that we are completting in the middle of an ident) + for segment in &path.segments { + let ident = &segment.ident; + + // Check if we are at the end of the ident + if self.byte_index == ident.span().end() as usize { + idents.push(ident.clone()); + break; + } + + // Check if we are in the middle of an ident + if self.includes_span(ident.span()) { + // If so, take the substring and push that as the list of idents + // we'll do autocompletion for + let offset = self.byte_index - ident.span().start() as usize; + let substring = ident.0.contents[0..offset].to_string(); + let ident = Ident::new( + substring, + Span::from(ident.span().start()..ident.span().start() + offset as u32), + ); + idents.push(ident); + in_the_middle = true; + break; + } + + idents.push(ident.clone()); + + // Stop if the cursor is right after this ident and '::' + if after_colons && self.byte_index == ident.span().end() as usize + 2 { + break; + } + } + + if idents.len() < path.segments.len() { + in_the_middle = true; + } + let prefix; let at_root; @@ -688,6 +728,21 @@ impl<'a> NodeFinder<'a> { let is_single_segment = !after_colons && idents.is_empty() && path.kind == PathKind::Plain; let module_id; + let module_completion_kind = if after_colons { + ModuleCompletionKind::DirectChildren + } else { + ModuleCompletionKind::AllVisibleItems + }; + + // When completing in the middle of an ident, we don't want to complete + // with function parameters because there might already be function parameters, + // and in the middle of a path it leads to code that won't compile + let function_completion_kind = if in_the_middle { + FunctionCompletionKind::Name + } else { + FunctionCompletionKind::NameAndParameters + }; + if idents.is_empty() { module_id = self.module_id; } else { @@ -703,6 +758,7 @@ impl<'a> NodeFinder<'a> { &Type::Struct(struct_type, vec![]), &prefix, FunctionKind::Any, + function_completion_kind, ); return; } @@ -713,25 +769,28 @@ impl<'a> NodeFinder<'a> { ModuleDefId::TypeAliasId(type_alias_id) => { let type_alias = self.interner.get_type_alias(type_alias_id); let type_alias = type_alias.borrow(); - self.complete_type_methods(&type_alias.typ, &prefix, FunctionKind::Any); + self.complete_type_methods( + &type_alias.typ, + &prefix, + FunctionKind::Any, + function_completion_kind, + ); return; } ModuleDefId::TraitId(trait_id) => { let trait_ = self.interner.get_trait(trait_id); - self.complete_trait_methods(trait_, &prefix, FunctionKind::Any); + self.complete_trait_methods( + trait_, + &prefix, + FunctionKind::Any, + function_completion_kind, + ); return; } ModuleDefId::GlobalId(_) => return, } } - let module_completion_kind = if after_colons { - ModuleCompletionKind::DirectChildren - } else { - ModuleCompletionKind::AllVisibleItems - }; - let function_completion_kind = FunctionCompletionKind::NameAndParameters; - self.complete_in_module( module_id, &prefix, @@ -746,7 +805,7 @@ impl<'a> NodeFinder<'a> { match requested_items { RequestedItems::AnyItems => { self.local_variables_completion(&prefix); - self.builtin_functions_completion(&prefix); + self.builtin_functions_completion(&prefix, function_completion_kind); self.builtin_values_completion(&prefix); } RequestedItems::OnlyTypes => { @@ -754,7 +813,7 @@ impl<'a> NodeFinder<'a> { self.type_parameters_completion(&prefix); } } - self.complete_auto_imports(&prefix, requested_items); + self.complete_auto_imports(&prefix, requested_items, function_completion_kind); } } @@ -959,10 +1018,21 @@ impl<'a> NodeFinder<'a> { | Type::Error => (), } - self.complete_type_methods(typ, prefix, FunctionKind::SelfType(typ)); + self.complete_type_methods( + typ, + prefix, + FunctionKind::SelfType(typ), + FunctionCompletionKind::NameAndParameters, + ); } - fn complete_type_methods(&mut self, typ: &Type, prefix: &str, function_kind: FunctionKind) { + fn complete_type_methods( + &mut self, + typ: &Type, + prefix: &str, + function_kind: FunctionKind, + function_completion_kind: FunctionCompletionKind, + ) { let Some(methods_by_name) = self.interner.get_type_methods(typ) else { return; }; @@ -972,7 +1042,7 @@ impl<'a> NodeFinder<'a> { if name_matches(name, prefix) { if let Some(completion_item) = self.function_completion_item( func_id, - FunctionCompletionKind::NameAndParameters, + function_completion_kind, function_kind, ) { self.completion_items.push(completion_item); @@ -988,14 +1058,13 @@ impl<'a> NodeFinder<'a> { trait_: &Trait, prefix: &str, function_kind: FunctionKind, + function_completion_kind: FunctionCompletionKind, ) { for (name, func_id) in &trait_.method_ids { if name_matches(name, prefix) { - if let Some(completion_item) = self.function_completion_item( - *func_id, - FunctionCompletionKind::NameAndParameters, - function_kind, - ) { + if let Some(completion_item) = + self.function_completion_item(*func_id, function_completion_kind, function_kind) + { self.completion_items.push(completion_item); self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id)); } diff --git a/tooling/lsp/src/requests/completion/auto_import.rs b/tooling/lsp/src/requests/completion/auto_import.rs index fa39c7a60d2..162e1616832 100644 --- a/tooling/lsp/src/requests/completion/auto_import.rs +++ b/tooling/lsp/src/requests/completion/auto_import.rs @@ -17,7 +17,12 @@ use super::{ }; impl<'a> NodeFinder<'a> { - pub(super) fn complete_auto_imports(&mut self, prefix: &str, requested_items: RequestedItems) { + pub(super) fn complete_auto_imports( + &mut self, + prefix: &str, + requested_items: RequestedItems, + function_completion_kind: FunctionCompletionKind, + ) { let current_module_parent_id = get_parent_module_id(self.def_maps, self.module_id); for (name, entries) in self.interner.get_auto_import_names() { @@ -33,7 +38,7 @@ impl<'a> NodeFinder<'a> { let Some(mut completion_item) = self.module_def_id_completion_item( *module_def_id, name.clone(), - FunctionCompletionKind::NameAndParameters, + function_completion_kind, FunctionKind::Any, requested_items, ) else { diff --git a/tooling/lsp/src/requests/completion/builtins.rs b/tooling/lsp/src/requests/completion/builtins.rs index 75eba7fb3c7..5066c5f4f2b 100644 --- a/tooling/lsp/src/requests/completion/builtins.rs +++ b/tooling/lsp/src/requests/completion/builtins.rs @@ -4,19 +4,38 @@ use strum::IntoEnumIterator; use super::{ completion_items::{simple_completion_item, snippet_completion_item}, + kinds::FunctionCompletionKind, name_matches, NodeFinder, }; impl<'a> NodeFinder<'a> { - pub(super) fn builtin_functions_completion(&mut self, prefix: &str) { + pub(super) fn builtin_functions_completion( + &mut self, + prefix: &str, + function_completion_kind: FunctionCompletionKind, + ) { for keyword in Keyword::iter() { if let Some(func) = keyword_builtin_function(&keyword) { if name_matches(func.name, prefix) { + let description = Some(func.description.to_string()); + let label; + let insert_text; + match function_completion_kind { + FunctionCompletionKind::Name => { + label = func.name.to_string(); + insert_text = func.name.to_string(); + } + FunctionCompletionKind::NameAndParameters => { + label = format!("{}(…)", func.name); + insert_text = format!("{}({})", func.name, func.parameters); + } + } + self.completion_items.push(snippet_completion_item( - format!("{}(…)", func.name), + label, CompletionItemKind::FUNCTION, - format!("{}({})", func.name, func.parameters), - Some(func.description.to_string()), + insert_text, + description, )); } } diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index 68d9910f5f9..d54636f5690 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1656,4 +1656,68 @@ mod completion_tests { Some("(use std::merkle::compute_merkle_root)".to_string()), ); } + + #[test] + async fn test_completes_after_first_letter_of_path() { + let src = r#" + fn main() { + h>||| Date: Thu, 22 Aug 2024 11:55:13 -0300 Subject: [PATCH 02/10] Complete in call name if it already arguments --- tooling/lsp/src/requests/completion.rs | 30 +++++++++++++++---- tooling/lsp/src/requests/completion/tests.rs | 22 ++++++++++++++ .../lsp/src/requests/completion/traversal.rs | 11 ++----- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index e829a552c3a..2d86b97ea7c 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -15,8 +15,8 @@ use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Completion use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - AsTraitPath, BlockExpression, ConstructorExpression, Expression, ExpressionKind, - ForLoopStatement, Ident, IfExpression, LValue, Lambda, LetStatement, + AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression, + ExpressionKind, ForLoopStatement, Ident, IfExpression, LValue, Lambda, LetStatement, MemberAccessExpression, NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, PathSegment, Pattern, Statement, StatementKind, TraitItem, TypeImpl, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UseTree, UseTreeKind, @@ -338,6 +338,18 @@ impl<'a> NodeFinder<'a> { } } + pub(super) fn find_in_call_expression(&mut self, call_expression: &CallExpression) { + if let ExpressionKind::Variable(path) = &call_expression.func.kind { + if self.includes_span(path.span) { + self.find_in_path_impl(path, RequestedItems::AnyItems, true); + return; + } + } + + self.find_in_expression(&call_expression.func); + self.find_in_expressions(&call_expression.arguments); + } + fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { let old_local_variables = self.local_variables.clone(); for statement in &block_expression.statements { @@ -663,6 +675,15 @@ impl<'a> NodeFinder<'a> { } fn find_in_path(&mut self, path: &Path, requested_items: RequestedItems) { + self.find_in_path_impl(path, requested_items, false) + } + + fn find_in_path_impl( + &mut self, + path: &Path, + requested_items: RequestedItems, + mut in_the_middle: bool, + ) { if !self.includes_span(path.span) { return; } @@ -671,9 +692,6 @@ impl<'a> NodeFinder<'a> { let mut idents: Vec = Vec::new(); - // Are we in the middle of an ident? - let mut in_the_middle = false; - // Find in which ident we are in, and in which part of it // (it could be that we are completting in the middle of an ident) for segment in &path.segments { @@ -728,7 +746,7 @@ impl<'a> NodeFinder<'a> { let is_single_segment = !after_colons && idents.is_empty() && path.kind == PathKind::Plain; let module_id; - let module_completion_kind = if after_colons { + let module_completion_kind = if after_colons || !idents.is_empty() { ModuleCompletionKind::DirectChildren } else { ModuleCompletionKind::AllVisibleItems diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index d54636f5690..0ca526bdb81 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1720,4 +1720,26 @@ mod completion_tests { ) .await } + + #[test] + async fn test_completes_at_function_call_name() { + let src = r#" + mod foo { + fn bar() {} + } + + fn main() { + foo::b>|<() + } + "#; + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item( + "bar", + CompletionItemKind::FUNCTION, + Some("fn()".to_string()), + )], + ) + .await + } } diff --git a/tooling/lsp/src/requests/completion/traversal.rs b/tooling/lsp/src/requests/completion/traversal.rs index b487c8baf36..47be4423430 100644 --- a/tooling/lsp/src/requests/completion/traversal.rs +++ b/tooling/lsp/src/requests/completion/traversal.rs @@ -2,9 +2,9 @@ /// traversing the AST without any additional logic. use noirc_frontend::{ ast::{ - ArrayLiteral, AssignStatement, CallExpression, CastExpression, ConstrainStatement, - Expression, ForRange, FunctionReturnType, IndexExpression, InfixExpression, Literal, - MethodCallExpression, NoirTrait, NoirTypeAlias, TraitImplItem, UnresolvedType, + ArrayLiteral, AssignStatement, CastExpression, ConstrainStatement, Expression, ForRange, + FunctionReturnType, IndexExpression, InfixExpression, Literal, MethodCallExpression, + NoirTrait, NoirTypeAlias, TraitImplItem, UnresolvedType, }, ParsedModule, }; @@ -89,11 +89,6 @@ impl<'a> NodeFinder<'a> { self.find_in_expression(&index_expression.index); } - pub(super) fn find_in_call_expression(&mut self, call_expression: &CallExpression) { - self.find_in_expression(&call_expression.func); - self.find_in_expressions(&call_expression.arguments); - } - pub(super) fn find_in_method_call_expression( &mut self, method_call_expression: &MethodCallExpression, From 45c206a3ef0d839594b318bdd65998592ae592e6 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 22 Aug 2024 11:57:43 -0300 Subject: [PATCH 03/10] Add a comment --- tooling/lsp/src/requests/completion.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 2d86b97ea7c..0622801ef69 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -339,6 +339,13 @@ impl<'a> NodeFinder<'a> { } pub(super) fn find_in_call_expression(&mut self, call_expression: &CallExpression) { + // Check if it's this case: + // + // foo::b>|<(...) + // + // In this case we want to suggest items in foo but if they are functions + // we don't want to insert arguments, because they are already there (even if + // they could be wrong) just because inserting them would lead to broken code. if let ExpressionKind::Variable(path) = &call_expression.func.kind { if self.includes_span(path.span) { self.find_in_path_impl(path, RequestedItems::AnyItems, true); From 3439281406271a07c73105c464a17f185eaa4fd8 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 22 Aug 2024 12:06:32 -0300 Subject: [PATCH 04/10] Complete in the middle of a method call name --- tooling/lsp/src/requests/completion.rs | 35 +++++++++++++++++-- tooling/lsp/src/requests/completion/tests.rs | 20 +++++++++++ .../lsp/src/requests/completion/traversal.rs | 12 ++----- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 0622801ef69..18b112b364a 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -17,9 +17,10 @@ use noirc_frontend::{ ast::{ AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression, ExpressionKind, ForLoopStatement, Ident, IfExpression, LValue, Lambda, LetStatement, - MemberAccessExpression, NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, - PathSegment, Pattern, Statement, StatementKind, TraitItem, TypeImpl, UnresolvedGeneric, - UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UseTree, UseTreeKind, + MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, NoirTraitImpl, + Path, PathKind, PathSegment, Pattern, Statement, StatementKind, TraitItem, TypeImpl, + UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UseTree, + UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ @@ -357,6 +358,34 @@ impl<'a> NodeFinder<'a> { self.find_in_expressions(&call_expression.arguments); } + pub(super) fn find_in_method_call_expression( + &mut self, + method_call_expression: &MethodCallExpression, + ) { + // Check if it's this case: + // + // foo.b>|<(...) + // + // In this case we want to suggest items in foo but if they are functions + // we don't want to insert arguments, because they are already there (even if + // they could be wrong) just because inserting them would lead to broken code. + if self.includes_span(method_call_expression.method_name.span()) { + let location = Location::new(method_call_expression.object.span, self.file); + if let Some(typ) = self.interner.type_at_location(location) { + let typ = typ.follow_bindings(); + let prefix = method_call_expression.method_name.to_string(); + let offset = + self.byte_index - method_call_expression.method_name.span().start() as usize; + let prefix = prefix[0..offset].to_string(); + self.complete_type_fields_and_methods(&typ, &prefix); + return; + } + } + + self.find_in_expression(&method_call_expression.object); + self.find_in_expressions(&method_call_expression.arguments); + } + fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { let old_local_variables = self.local_variables.clone(); for statement in &block_expression.statements { diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index 0ca526bdb81..b7ef40c0a0f 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1742,4 +1742,24 @@ mod completion_tests { ) .await } + + #[test] + async fn test_completes_at_method_call_name() { + let src = r#" + struct Foo {} + + impl Foo { + pub fn bar(self) {} + } + + fn x(f: Foo) { + f.b>| NodeFinder<'a> { self.find_in_expression(&index_expression.index); } - pub(super) fn find_in_method_call_expression( - &mut self, - method_call_expression: &MethodCallExpression, - ) { - self.find_in_expression(&method_call_expression.object); - self.find_in_expressions(&method_call_expression.arguments); - } - pub(super) fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { self.find_in_expression(&cast_expression.lhs); } From 176d2610e3823c7abb1fcde70f2242ba15eb7c05 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 22 Aug 2024 12:07:05 -0300 Subject: [PATCH 05/10] Make test a bit more robust --- tooling/lsp/src/requests/completion/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index b7ef40c0a0f..4dfdc6ca177 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1729,7 +1729,7 @@ mod completion_tests { } fn main() { - foo::b>|<() + foo::b>| Date: Thu, 22 Aug 2024 12:07:55 -0300 Subject: [PATCH 06/10] clippy --- tooling/lsp/src/requests/completion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 18b112b364a..5780ddb31be 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -711,7 +711,7 @@ impl<'a> NodeFinder<'a> { } fn find_in_path(&mut self, path: &Path, requested_items: RequestedItems) { - self.find_in_path_impl(path, requested_items, false) + self.find_in_path_impl(path, requested_items, false); } fn find_in_path_impl( From ed8e1c83b98a3d3453b192a0ddf294975f15deac Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 22 Aug 2024 12:12:59 -0300 Subject: [PATCH 07/10] Actually make it work as intended --- tooling/lsp/src/requests/completion.rs | 41 ++++++++++++++++---- tooling/lsp/src/requests/completion/tests.rs | 6 ++- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 5780ddb31be..9034f754708 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -377,7 +377,7 @@ impl<'a> NodeFinder<'a> { let offset = self.byte_index - method_call_expression.method_name.span().start() as usize; let prefix = prefix[0..offset].to_string(); - self.complete_type_fields_and_methods(&typ, &prefix); + self.complete_type_fields_and_methods(&typ, &prefix, FunctionCompletionKind::Name); return; } } @@ -466,7 +466,11 @@ impl<'a> NodeFinder<'a> { { let typ = self.interner.definition_type(definition_id); let prefix = ""; - self.complete_type_fields_and_methods(&typ, prefix); + self.complete_type_fields_and_methods( + &typ, + prefix, + FunctionCompletionKind::NameAndParameters, + ); } } } @@ -556,7 +560,11 @@ impl<'a> NodeFinder<'a> { if let Some(typ) = self.interner.type_at_location(location) { let typ = typ.follow_bindings(); let prefix = ""; - self.complete_type_fields_and_methods(&typ, prefix); + self.complete_type_fields_and_methods( + &typ, + prefix, + FunctionCompletionKind::NameAndParameters, + ); } } } @@ -618,7 +626,11 @@ impl<'a> NodeFinder<'a> { if let Some(typ) = self.interner.type_at_location(location) { let typ = typ.follow_bindings(); let prefix = ident.to_string().to_case(Case::Snake); - self.complete_type_fields_and_methods(&typ, &prefix); + self.complete_type_fields_and_methods( + &typ, + &prefix, + FunctionCompletionKind::NameAndParameters, + ); return; } } @@ -1038,17 +1050,30 @@ impl<'a> NodeFinder<'a> { }; } - fn complete_type_fields_and_methods(&mut self, typ: &Type, prefix: &str) { + fn complete_type_fields_and_methods( + &mut self, + typ: &Type, + prefix: &str, + function_completion_kind: FunctionCompletionKind, + ) { match typ { Type::Struct(struct_type, generics) => { self.complete_struct_fields(&struct_type.borrow(), generics, prefix); } Type::MutableReference(typ) => { - return self.complete_type_fields_and_methods(typ, prefix); + return self.complete_type_fields_and_methods( + typ, + prefix, + function_completion_kind, + ); } Type::Alias(type_alias, _) => { let type_alias = type_alias.borrow(); - return self.complete_type_fields_and_methods(&type_alias.typ, prefix); + return self.complete_type_fields_and_methods( + &type_alias.typ, + prefix, + function_completion_kind, + ); } Type::Tuple(types) => { self.complete_tuple_fields(types); @@ -1076,7 +1101,7 @@ impl<'a> NodeFinder<'a> { typ, prefix, FunctionKind::SelfType(typ), - FunctionCompletionKind::NameAndParameters, + function_completion_kind, ); } diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index 4dfdc6ca177..3eb369cc524 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1758,7 +1758,11 @@ mod completion_tests { "#; assert_completion_excluding_auto_import( src, - vec![function_completion_item("bar()", "bar()", "fn(self)")], + vec![simple_completion_item( + "bar", + CompletionItemKind::FUNCTION, + Some("fn(self)".to_string()), + )], ) .await } From c55711ffb7329d1388e8ecbb95d6b6b00147bc3c Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 22 Aug 2024 12:57:07 -0300 Subject: [PATCH 08/10] Also handle the case of completion right after a dot --- tooling/lsp/src/requests/completion.rs | 18 +++++++++++++++ tooling/lsp/src/requests/completion/tests.rs | 24 ++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 9034f754708..d86ff0269cc 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -354,6 +354,24 @@ impl<'a> NodeFinder<'a> { } } + // Check if it's this case: + // + // foo.>|<(...) + // + // "foo." is actually broken, but it's parsed as "foo", so this is seen + // as "foo(...)" but if we are at a dot right after "foo" it means it's + // the above case and we want to suggest methods of foo's type. + let after_dot = self.byte == Some(b'.'); + if after_dot && call_expression.func.span.end() as usize == self.byte_index - 1 { + let location = Location::new(call_expression.func.span, self.file); + if let Some(typ) = self.interner.type_at_location(location) { + let typ = typ.follow_bindings(); + let prefix = ""; + self.complete_type_fields_and_methods(&typ, prefix, FunctionCompletionKind::Name); + return; + } + } + self.find_in_expression(&call_expression.func); self.find_in_expressions(&call_expression.arguments); } diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index 3eb369cc524..55b68bee8eb 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1766,4 +1766,28 @@ mod completion_tests { ) .await } + + #[test] + async fn test_completes_at_method_call_name_after_dot() { + let src = r#" + struct Foo {} + + impl Foo { + pub fn bar(self) {} + } + + fn x(f: Foo) { + f.>|<() + } + "#; + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item( + "bar", + CompletionItemKind::FUNCTION, + Some("fn(self)".to_string()), + )], + ) + .await + } } From 7fbd4f8a441129060bc3ac3f72d391e4049fc157 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 22 Aug 2024 13:06:59 -0300 Subject: [PATCH 09/10] More clippy --- tooling/lsp/src/requests/completion/tests.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index 55b68bee8eb..07ac8885bd5 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1674,7 +1674,7 @@ mod completion_tests { Some("fn()".to_string()), )], ) - .await + .await; } #[test] @@ -1696,7 +1696,7 @@ mod completion_tests { Some("fn()".to_string()), )], ) - .await + .await; } #[test] @@ -1718,7 +1718,7 @@ mod completion_tests { Some("fn()".to_string()), )], ) - .await + .await; } #[test] @@ -1740,7 +1740,7 @@ mod completion_tests { Some("fn()".to_string()), )], ) - .await + .await; } #[test] @@ -1764,7 +1764,7 @@ mod completion_tests { Some("fn(self)".to_string()), )], ) - .await + .await; } #[test] @@ -1788,6 +1788,6 @@ mod completion_tests { Some("fn(self)".to_string()), )], ) - .await + .await; } } From 8d2b4a3aec16b0d80a5d34e04b097fc46d50e76f Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 22 Aug 2024 19:10:01 -0300 Subject: [PATCH 10/10] Spellcheck --- docs/docs/noir/concepts/traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/noir/concepts/traits.md b/docs/docs/noir/concepts/traits.md index 912a84a010c..597c62c737c 100644 --- a/docs/docs/noir/concepts/traits.md +++ b/docs/docs/noir/concepts/traits.md @@ -227,7 +227,7 @@ fn main() { ### Associated Types and Constants -Traits also support associated types and constaints which can be thought of as additional generics that are referred to by name. +Traits also support associated types and constraints which can be thought of as additional generics that are referred to by name. Here's an example of a trait with an associated type `Foo` and a constant `Bar`: