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`: diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 4a22999f7a3..15b5be1690c 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -15,10 +15,12 @@ use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Completion use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - AsTraitPath, BlockExpression, ConstructorExpression, Expression, ForLoopStatement, Ident, - IfExpression, LValue, Lambda, LetStatement, MemberAccessExpression, NoirFunction, - NoirStruct, NoirTraitImpl, Path, PathKind, PathSegment, Pattern, Statement, TraitItem, - TypeImpl, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind, + AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression, + ExpressionKind, ForLoopStatement, Ident, IfExpression, LValue, Lambda, LetStatement, + MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, NoirTraitImpl, + Path, PathKind, PathSegment, Pattern, Statement, StatementKind, TraitItem, TypeImpl, + UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UseTree, + UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ @@ -26,7 +28,7 @@ use noirc_frontend::{ resolution::path_resolver::{PathResolver, StandardPathResolver}, }, hir_def::traits::Trait, - macros_api::{ExpressionKind, ModuleDefId, NodeInterner, StatementKind, UnresolvedTypeData}, + macros_api::{ModuleDefId, NodeInterner}, node_interner::ReferenceId, parser::{Item, ItemKind}, ParsedModule, StructType, Type, @@ -337,6 +339,71 @@ 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); + return; + } + } + + // 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); + } + + 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, FunctionCompletionKind::Name); + 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 { @@ -417,7 +484,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, + ); } } } @@ -507,7 +578,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, + ); } } } @@ -569,7 +644,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; } } @@ -662,15 +741,61 @@ 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 { + 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; } 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(); + + // 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; @@ -687,6 +812,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 || !idents.is_empty() { + 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 { @@ -702,6 +842,7 @@ impl<'a> NodeFinder<'a> { &Type::Struct(struct_type, vec![]), &prefix, FunctionKind::Any, + function_completion_kind, ); return; } @@ -712,25 +853,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, @@ -745,7 +889,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 => { @@ -753,7 +897,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); } } @@ -924,17 +1068,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); @@ -958,10 +1115,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), + function_completion_kind, + ); } - 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; }; @@ -971,7 +1139,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); @@ -987,14 +1155,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 6685c9f4298..b9c4ce2358a 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..07ac8885bd5 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -1656,4 +1656,138 @@ 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>||||||<() + } + "#; + assert_completion_excluding_auto_import( + src, + vec![simple_completion_item( + "bar", + CompletionItemKind::FUNCTION, + Some("fn(self)".to_string()), + )], + ) + .await; + } } diff --git a/tooling/lsp/src/requests/completion/traversal.rs b/tooling/lsp/src/requests/completion/traversal.rs index 69ef8bdd2e8..e3bd8ffadf7 100644 --- a/tooling/lsp/src/requests/completion/traversal.rs +++ b/tooling/lsp/src/requests/completion/traversal.rs @@ -2,10 +2,9 @@ /// traversing the AST without any additional logic. use noirc_frontend::{ ast::{ - ArrayLiteral, AssignStatement, CallExpression, CastExpression, ConstrainStatement, - Expression, ForRange, FunctionReturnType, GenericTypeArgs, IndexExpression, - InfixExpression, Literal, MethodCallExpression, NoirTrait, NoirTypeAlias, TraitImplItem, - UnresolvedType, + ArrayLiteral, AssignStatement, CastExpression, ConstrainStatement, Expression, ForRange, + FunctionReturnType, GenericTypeArgs, IndexExpression, InfixExpression, Literal, NoirTrait, + NoirTypeAlias, TraitImplItem, UnresolvedType, }, ParsedModule, }; @@ -90,19 +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, - ) { - 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); }