diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 3eacc8915ac..ad2082d75a8 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -4,12 +4,10 @@ use std::{ }; use async_lsp::ResponseError; -use builtins::{builtin_integer_types, keyword_builtin_function, keyword_builtin_type}; +use completion_items::{crate_completion_item, simple_completion_item}; use fm::{FileId, PathString}; -use lsp_types::{ - CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionParams, - CompletionResponse, InsertTextFormat, -}; +use kinds::{FunctionCompletionKind, FunctionKind, ModuleCompletionKind, RequestedItems}; +use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse}; use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ @@ -26,61 +24,22 @@ use noirc_frontend::{ def_map::{CrateDefMap, LocalModuleId, ModuleId}, resolution::path_resolver::{PathResolver, StandardPathResolver}, }, - hir_def::{function::FuncMeta, stmt::HirPattern}, - macros_api::{ModuleDefId, NodeInterner, StructId}, - node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, + macros_api::{ModuleDefId, NodeInterner}, + node_interner::ReferenceId, parser::{Item, ItemKind}, - token::Keyword, ParsedModule, StructType, Type, }; -use strum::IntoEnumIterator; +use sort_text::underscore_sort_text; use crate::{utils, LspState}; use super::process_request; mod builtins; - -/// When finding items in a module, whether to show only direct children or all visible items. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum ModuleCompletionKind { - // Only show a module's direct children. This is used when completing a use statement - // or a path after the first segment. - DirectChildren, - // Show all of a module's visible items. This is used when completing a path outside - // of a use statement (in regular code) when the path is just a single segment: - // we want to find items exposed in the current module. - AllVisibleItems, -} - -/// When suggest a function as a result of completion, whether to autocomplete its name or its name and parameters. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum FunctionCompletionKind { - // Only complete a function's name. This is used in use statement. - Name, - // Complete a function's name and parameters (as a snippet). This is used in regular code. - NameAndParameters, -} - -/// Is there a requirement for suggesting functions? -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum FunctionKind<'a> { - /// No requirement: any function is okay to suggest. - Any, - /// Only show functions that have the given self type. - SelfType(&'a Type), -} - -/// When requesting completions, whether to list all items or just types. -/// For example, when writing `let x: S` we only want to suggest types at this -/// point (modules too, because they might include types too). -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum RequestedItems { - // Suggest any items (types, functions, etc.). - AnyItems, - // Only suggest types. - OnlyTypes, -} +mod completion_items; +mod kinds; +mod sort_text; +mod tests; pub(crate) fn on_completion_request( state: &mut LspState, @@ -1180,221 +1139,6 @@ impl<'a> NodeFinder<'a> { } } - fn module_def_id_completion_item( - &self, - module_def_id: ModuleDefId, - name: String, - function_completion_kind: FunctionCompletionKind, - function_kind: FunctionKind, - requested_items: RequestedItems, - ) -> Option { - match requested_items { - RequestedItems::OnlyTypes => match module_def_id { - ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return None, - ModuleDefId::ModuleId(_) - | ModuleDefId::TypeId(_) - | ModuleDefId::TypeAliasId(_) - | ModuleDefId::TraitId(_) => (), - }, - RequestedItems::AnyItems => (), - } - - match module_def_id { - ModuleDefId::ModuleId(_) => Some(module_completion_item(name)), - ModuleDefId::FunctionId(func_id) => { - self.function_completion_item(func_id, function_completion_kind, function_kind) - } - ModuleDefId::TypeId(struct_id) => Some(self.struct_completion_item(struct_id)), - ModuleDefId::TypeAliasId(type_alias_id) => { - Some(self.type_alias_completion_item(type_alias_id)) - } - ModuleDefId::TraitId(trait_id) => Some(self.trait_completion_item(trait_id)), - ModuleDefId::GlobalId(global_id) => Some(self.global_completion_item(global_id)), - } - } - - fn function_completion_item( - &self, - func_id: FuncId, - function_completion_kind: FunctionCompletionKind, - function_kind: FunctionKind, - ) -> Option { - let func_meta = self.interner.function_meta(&func_id); - let name = &self.interner.function_name(&func_id).to_string(); - - let func_self_type = if let Some((pattern, typ, _)) = func_meta.parameters.0.get(0) { - if self.hir_pattern_is_self_type(pattern) { - if let Type::MutableReference(mut_typ) = typ { - let typ: &Type = mut_typ; - Some(typ) - } else { - Some(typ) - } - } else { - None - } - } else { - None - }; - - match function_kind { - FunctionKind::Any => (), - FunctionKind::SelfType(mut self_type) => { - if let Some(func_self_type) = func_self_type { - if matches!(self_type, Type::Integer(..)) - || matches!(self_type, Type::FieldElement) - { - // Check that the pattern type is the same as self type. - // We do this because some types (only Field and integer types) - // have their methods in the same HashMap. - - if let Type::MutableReference(mut_typ) = self_type { - self_type = mut_typ; - } - - if self_type != func_self_type { - return None; - } - } - } else { - return None; - } - } - } - - let is_operator = if let Some(trait_impl_id) = &func_meta.trait_impl { - let trait_impl = self.interner.get_trait_implementation(*trait_impl_id); - let trait_impl = trait_impl.borrow(); - self.interner.is_operator_trait(trait_impl.trait_id) - } else { - false - }; - let description = func_meta_type_to_string(func_meta, func_self_type.is_some()); - - let completion_item = match function_completion_kind { - FunctionCompletionKind::Name => { - simple_completion_item(name, CompletionItemKind::FUNCTION, Some(description)) - } - FunctionCompletionKind::NameAndParameters => { - let kind = CompletionItemKind::FUNCTION; - let insert_text = self.compute_function_insert_text(func_meta, name, function_kind); - let label = if insert_text.ends_with("()") { - format!("{}()", name) - } else { - format!("{}(…)", name) - }; - - snippet_completion_item(label, kind, insert_text, Some(description)) - } - }; - - let completion_item = if is_operator { - completion_item_with_sort_text(completion_item, operator_sort_text()) - } else if function_kind == FunctionKind::Any && name == "new" { - completion_item_with_sort_text(completion_item, new_sort_text()) - } else if function_kind == FunctionKind::Any && func_self_type.is_some() { - completion_item_with_sort_text(completion_item, self_mismatch_sort_text()) - } else { - completion_item - }; - - Some(completion_item) - } - - fn compute_function_insert_text( - &self, - func_meta: &FuncMeta, - name: &str, - function_kind: FunctionKind, - ) -> String { - let mut text = String::new(); - text.push_str(name); - text.push('('); - - let mut index = 1; - for (pattern, _, _) in &func_meta.parameters.0 { - if index == 1 { - match function_kind { - FunctionKind::SelfType(_) => { - if self.hir_pattern_is_self_type(pattern) { - continue; - } - } - FunctionKind::Any => (), - } - } - - if index > 1 { - text.push_str(", "); - } - - text.push_str("${"); - text.push_str(&index.to_string()); - text.push(':'); - self.hir_pattern_to_argument(pattern, &mut text); - text.push('}'); - - index += 1; - } - text.push(')'); - text - } - - fn hir_pattern_to_argument(&self, pattern: &HirPattern, text: &mut String) { - match pattern { - HirPattern::Identifier(hir_ident) => { - text.push_str(self.interner.definition_name(hir_ident.id)); - } - HirPattern::Mutable(pattern, _) => self.hir_pattern_to_argument(pattern, text), - HirPattern::Tuple(_, _) | HirPattern::Struct(_, _, _) => text.push('_'), - } - } - - fn hir_pattern_is_self_type(&self, pattern: &HirPattern) -> bool { - match pattern { - HirPattern::Identifier(hir_ident) => { - let name = self.interner.definition_name(hir_ident.id); - name == "self" || name == "_self" - } - HirPattern::Mutable(pattern, _) => self.hir_pattern_is_self_type(pattern), - HirPattern::Tuple(_, _) | HirPattern::Struct(_, _, _) => false, - } - } - - fn struct_completion_item(&self, struct_id: StructId) -> CompletionItem { - let struct_type = self.interner.get_struct(struct_id); - let struct_type = struct_type.borrow(); - let name = struct_type.name.to_string(); - - simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)) - } - - fn type_alias_completion_item(&self, type_alias_id: TypeAliasId) -> CompletionItem { - let type_alias = self.interner.get_type_alias(type_alias_id); - let type_alias = type_alias.borrow(); - let name = type_alias.name.to_string(); - - simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)) - } - - fn trait_completion_item(&self, trait_id: TraitId) -> CompletionItem { - let trait_ = self.interner.get_trait(trait_id); - let name = trait_.name.to_string(); - - simple_completion_item(name.clone(), CompletionItemKind::INTERFACE, Some(name)) - } - - fn global_completion_item(&self, global_id: GlobalId) -> CompletionItem { - let global_definition = self.interner.get_global_definition(global_id); - let name = global_definition.name.clone(); - - let global = self.interner.get_global(global_id); - let typ = self.interner.definition_type(global.definition_id); - let description = typ.to_string(); - - simple_completion_item(name, CompletionItemKind::CONSTANT, Some(description)) - } - fn resolve_module(&self, segments: Vec) -> Option { if let Some(ModuleDefId::ModuleId(module_id)) = self.resolve_path(segments) { Some(module_id) @@ -1425,57 +1169,6 @@ impl<'a> NodeFinder<'a> { None } - fn builtin_functions_completion(&mut self, prefix: &str) { - for keyword in Keyword::iter() { - if let Some(func) = keyword_builtin_function(&keyword) { - if name_matches(func.name, prefix) { - self.completion_items.push(snippet_completion_item( - format!("{}(…)", func.name), - CompletionItemKind::FUNCTION, - format!("{}({})", func.name, func.parameters), - Some(func.description.to_string()), - )); - } - } - } - } - - fn builtin_values_completion(&mut self, prefix: &str) { - for keyword in ["false", "true"] { - if name_matches(keyword, prefix) { - self.completion_items.push(simple_completion_item( - keyword, - CompletionItemKind::KEYWORD, - Some("bool".to_string()), - )); - } - } - } - - fn builtin_types_completion(&mut self, prefix: &str) { - for keyword in Keyword::iter() { - if let Some(typ) = keyword_builtin_type(&keyword) { - if name_matches(typ, prefix) { - self.completion_items.push(simple_completion_item( - typ, - CompletionItemKind::STRUCT, - Some(typ.to_string()), - )); - } - } - } - - for typ in builtin_integer_types() { - if name_matches(typ, prefix) { - self.completion_items.push(simple_completion_item( - typ, - CompletionItemKind::STRUCT, - Some(typ.to_string()), - )); - } - } - } - fn includes_span(&self, span: Span) -> bool { span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize } @@ -1485,76 +1178,6 @@ fn name_matches(name: &str, prefix: &str) -> bool { name.starts_with(prefix) } -fn module_completion_item(name: impl Into) -> CompletionItem { - simple_completion_item(name, CompletionItemKind::MODULE, None) -} - -fn crate_completion_item(name: impl Into) -> CompletionItem { - simple_completion_item(name, CompletionItemKind::MODULE, None) -} - -fn simple_completion_item( - label: impl Into, - kind: CompletionItemKind, - description: Option, -) -> CompletionItem { - CompletionItem { - label: label.into(), - label_details: Some(CompletionItemLabelDetails { detail: None, description }), - kind: Some(kind), - detail: None, - documentation: None, - deprecated: None, - preselect: None, - sort_text: Some(default_sort_text()), - filter_text: None, - insert_text: None, - insert_text_format: None, - insert_text_mode: None, - text_edit: None, - additional_text_edits: None, - command: None, - commit_characters: None, - data: None, - tags: None, - } -} - -fn snippet_completion_item( - label: impl Into, - kind: CompletionItemKind, - insert_text: impl Into, - description: Option, -) -> CompletionItem { - CompletionItem { - label: label.into(), - label_details: Some(CompletionItemLabelDetails { detail: None, description }), - kind: Some(kind), - insert_text_format: Some(InsertTextFormat::SNIPPET), - insert_text: Some(insert_text.into()), - detail: None, - documentation: None, - deprecated: None, - preselect: None, - sort_text: Some(default_sort_text()), - filter_text: None, - insert_text_mode: None, - text_edit: None, - additional_text_edits: None, - command: None, - commit_characters: None, - data: None, - tags: None, - } -} - -fn completion_item_with_sort_text( - completion_item: CompletionItem, - sort_text: String, -) -> CompletionItem { - CompletionItem { sort_text: Some(sort_text), ..completion_item } -} - fn module_def_id_from_reference_id(reference_id: ReferenceId) -> Option { match reference_id { ReferenceId::Module(module_id) => Some(ModuleDefId::ModuleId(module_id)), @@ -1568,1416 +1191,3 @@ fn module_def_id_from_reference_id(reference_id: ReferenceId) -> Option None, } } - -fn func_meta_type_to_string(func_meta: &FuncMeta, has_self_type: bool) -> String { - let mut typ = &func_meta.typ; - if let Type::Forall(_, typ_) = typ { - typ = typ_; - } - - if let Type::Function(args, ret, _env, unconstrained) = typ { - let mut string = String::new(); - if *unconstrained { - string.push_str("unconstrained "); - } - string.push_str("fn("); - for (index, arg) in args.iter().enumerate() { - if index > 0 { - string.push_str(", "); - } - if index == 0 && has_self_type { - type_to_self_string(arg, &mut string); - } else { - string.push_str(&arg.to_string()); - } - } - string.push(')'); - - let ret: &Type = ret; - if let Type::Unit = ret { - // Nothing - } else { - string.push_str(" -> "); - string.push_str(&ret.to_string()); - } - string - } else { - typ.to_string() - } -} - -fn type_to_self_string(typ: &Type, string: &mut String) { - if let Type::MutableReference(..) = typ { - string.push_str("&mut self"); - } else { - string.push_str("self"); - } -} - -/// Sort text for "new" methods: we want these to show up before anything else, -/// if we are completing at something like `Foo::` -fn new_sort_text() -> String { - "3".to_string() -} - -/// This is the default sort text. -fn default_sort_text() -> String { - "5".to_string() -} - -/// When completing something like `Foo::`, we want to show methods that take -/// self after the other ones. -fn self_mismatch_sort_text() -> String { - "7".to_string() -} - -/// We want to show operator methods last. -fn operator_sort_text() -> String { - "8".to_string() -} - -/// If a name begins with underscore it's likely something that's meant to -/// be private (but visibility doesn't exist everywhere yet, so for now -/// we assume that) -fn underscore_sort_text() -> String { - "9".to_string() -} - -#[cfg(test)] -mod completion_tests { - use crate::{notifications::on_did_open_text_document, test_utils}; - - use super::*; - use lsp_types::{ - DidOpenTextDocumentParams, PartialResultParams, Position, TextDocumentIdentifier, - TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams, - }; - use tokio::test; - - async fn assert_completion(src: &str, expected: Vec) { - let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; - - let (line, column) = src - .lines() - .enumerate() - .filter_map(|(line_index, line)| { - line.find(">|<").map(|char_index| (line_index, char_index)) - }) - .next() - .expect("Expected to find one >|< in the source code"); - - let src = src.replace(">|<", ""); - - on_did_open_text_document( - &mut state, - DidOpenTextDocumentParams { - text_document: TextDocumentItem { - uri: noir_text_document.clone(), - language_id: "noir".to_string(), - version: 0, - text: src.to_string(), - }, - }, - ); - - // Get inlay hints. These should now be relative to the changed text, - // not the saved file's text. - let response = on_completion_request( - &mut state, - CompletionParams { - text_document_position: TextDocumentPositionParams { - text_document: TextDocumentIdentifier { uri: noir_text_document }, - position: Position { line: line as u32, character: column as u32 }, - }, - work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, - partial_result_params: PartialResultParams { partial_result_token: None }, - context: None, - }, - ) - .await - .expect("Could not execute on_completion_request") - .unwrap(); - - let CompletionResponse::Array(items) = response else { - panic!("Expected response to be CompletionResponse::Array"); - }; - - let mut items = items.clone(); - items.sort_by_key(|item| item.label.clone()); - - let mut expected = expected.clone(); - expected.sort_by_key(|item| item.label.clone()); - - if items != expected { - println!( - "Items: {:?}", - items.iter().map(|item| item.label.clone()).collect::>() - ); - println!( - "Expected: {:?}", - expected.iter().map(|item| item.label.clone()).collect::>() - ); - } - - assert_eq!(items, expected); - } - - #[test] - async fn test_use_first_segment() { - let src = r#" - mod foo {} - mod foobar {} - use f>|< - "#; - - assert_completion( - src, - vec![module_completion_item("foo"), module_completion_item("foobar")], - ) - .await; - } - - #[test] - async fn test_use_second_segment() { - let src = r#" - mod foo { - mod bar {} - mod baz {} - } - use foo::>|< - "#; - - assert_completion(src, vec![module_completion_item("bar"), module_completion_item("baz")]) - .await; - } - - #[test] - async fn test_use_second_segment_after_typing() { - let src = r#" - mod foo { - mod bar {} - mod brave {} - } - use foo::ba>|< - "#; - - assert_completion(src, vec![module_completion_item("bar")]).await; - } - - #[test] - async fn test_use_struct() { - let src = r#" - mod foo { - struct Foo {} - } - use foo::>|< - "#; - - assert_completion( - src, - vec![simple_completion_item( - "Foo", - CompletionItemKind::STRUCT, - Some("Foo".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_use_function() { - let src = r#" - mod foo { - fn bar(x: i32) -> u64 { 0 } - } - use foo::>|< - "#; - - assert_completion( - src, - vec![simple_completion_item( - "bar", - CompletionItemKind::FUNCTION, - Some("fn(i32) -> u64".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_use_after_crate_and_letter() { - // Prove that "std" shows up - let src = r#" - use s>|< - "#; - assert_completion(src, vec![crate_completion_item("std")]).await; - - // "std" doesn't show up anymore because of the "crate::" prefix - let src = r#" - mod something {} - use crate::s>|< - "#; - assert_completion(src, vec![module_completion_item("something")]).await; - } - - #[test] - async fn test_use_suggests_hardcoded_crate() { - let src = r#" - use c>|< - "#; - - assert_completion( - src, - vec![simple_completion_item("crate::", CompletionItemKind::KEYWORD, None)], - ) - .await; - } - - #[test] - async fn test_use_in_tree_after_letter() { - let src = r#" - mod foo { - mod bar {} - } - use foo::{b>|<} - "#; - - assert_completion(src, vec![module_completion_item("bar")]).await; - } - - #[test] - async fn test_use_in_tree_after_colons() { - let src = r#" - mod foo { - mod bar { - mod baz {} - } - } - use foo::{bar::>|<} - "#; - - assert_completion(src, vec![module_completion_item("baz")]).await; - } - - #[test] - async fn test_use_in_tree_after_colons_after_another_segment() { - let src = r#" - mod foo { - mod bar {} - mod qux {} - } - use foo::{bar, q>|<} - "#; - - assert_completion(src, vec![module_completion_item("qux")]).await; - } - - #[test] - async fn test_use_in_nested_module() { - let src = r#" - mod foo { - mod something {} - - use s>|< - } - "#; - - assert_completion( - src, - vec![ - module_completion_item("something"), - crate_completion_item("std"), - simple_completion_item("super::", CompletionItemKind::KEYWORD, None), - ], - ) - .await; - } - - #[test] - async fn test_use_after_super() { - let src = r#" - mod foo {} - - mod bar { - mod something {} - - use super::f>|< - } - "#; - - assert_completion(src, vec![module_completion_item("foo")]).await; - } - - #[test] - async fn test_use_after_crate_and_letter_nested_in_module() { - let src = r#" - mod something { - mod something_else {} - use crate::s>|< - } - - "#; - assert_completion(src, vec![module_completion_item("something")]).await; - } - - #[test] - async fn test_use_after_crate_segment_and_letter_nested_in_module() { - let src = r#" - mod something { - mod something_else {} - use crate::something::s>|< - } - - "#; - assert_completion(src, vec![module_completion_item("something_else")]).await; - } - - #[test] - async fn test_complete_path_shows_module() { - let src = r#" - mod foobar {} - - fn main() { - fo>|< - } - "#; - assert_completion(src, vec![module_completion_item("foobar")]).await; - } - - #[test] - async fn test_complete_path_after_colons_shows_submodule() { - let src = r#" - mod foo { - mod bar {} - } - - fn main() { - foo::>|< - } - "#; - assert_completion(src, vec![module_completion_item("bar")]).await; - } - - #[test] - async fn test_complete_path_after_colons_and_letter_shows_submodule() { - let src = r#" - mod foo { - mod bar {} - } - - fn main() { - foo::b>|< - } - "#; - assert_completion(src, vec![module_completion_item("bar")]).await; - } - - #[test] - async fn test_complete_path_with_local_variable() { - let src = r#" - fn main() { - let local = 1; - l>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "local", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_complete_path_with_shadowed_local_variable() { - let src = r#" - fn main() { - let local = 1; - let local = true; - l>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "local", - CompletionItemKind::VARIABLE, - Some("bool".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_complete_path_with_function_argument() { - let src = r#" - fn main(local: Field) { - l>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "local", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_complete_function_without_arguments() { - let src = r#" - fn hello() { } - - fn main() { - h>|< - } - "#; - assert_completion( - src, - vec![snippet_completion_item( - "hello()", - CompletionItemKind::FUNCTION, - "hello()", - Some("fn()".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_complete_function() { - let src = r#" - fn hello(x: i32, y: Field) { } - - fn main() { - h>|< - } - "#; - assert_completion( - src, - vec![snippet_completion_item( - "hello(…)", - CompletionItemKind::FUNCTION, - "hello(${1:x}, ${2:y})", - Some("fn(i32, Field)".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_complete_builtin_functions() { - let src = r#" - fn main() { - a>|< - } - "#; - assert_completion( - src, - vec![ - snippet_completion_item( - "assert(…)", - CompletionItemKind::FUNCTION, - "assert(${1:predicate})", - Some("fn(T)".to_string()), - ), - snippet_completion_item( - "assert_constant(…)", - CompletionItemKind::FUNCTION, - "assert_constant(${1:x})", - Some("fn(T)".to_string()), - ), - snippet_completion_item( - "assert_eq(…)", - CompletionItemKind::FUNCTION, - "assert_eq(${1:lhs}, ${2:rhs})", - Some("fn(T, T)".to_string()), - ), - ], - ) - .await; - } - - #[test] - async fn test_complete_path_in_impl() { - let src = r#" - struct SomeStruct {} - - impl SomeStruct { - fn foo() { - S>|< - } - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "SomeStruct", - CompletionItemKind::STRUCT, - Some("SomeStruct".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_complete_path_in_trait_impl() { - let src = r#" - struct SomeStruct {} - trait Trait {} - - impl Trait for SomeStruct { - fn foo() { - S>|< - } - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "SomeStruct", - CompletionItemKind::STRUCT, - Some("SomeStruct".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_complete_path_with_for_argument() { - let src = r#" - fn main() { - for index in 0..10 { - i>|< - } - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "index", - CompletionItemKind::VARIABLE, - Some("u32".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_complete_path_with_lambda_argument() { - let src = r#" - fn lambda(f: fn(i32)) { } - - fn main() { - lambda(|var| v>|<) - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "var", - CompletionItemKind::VARIABLE, - Some("_".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_type_in_struct_field_type() { - let src = r#" - struct Something {} - - fn SomeFunction() {} - - struct Another { - some: So>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "Something", - CompletionItemKind::STRUCT, - Some("Something".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_type_in_function_parameter() { - let src = r#" - struct Something {} - - fn foo(x: So>|<) {} - "#; - assert_completion( - src, - vec![simple_completion_item( - "Something", - CompletionItemKind::STRUCT, - Some("Something".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_type_in_function_return_type() { - let src = r#" - struct Something {} - - fn foo() -> So>|< {} - "#; - assert_completion( - src, - vec![simple_completion_item( - "Something", - CompletionItemKind::STRUCT, - Some("Something".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_type_in_type_alias() { - let src = r#" - struct Something {} - - type Foo = So>|< - "#; - assert_completion( - src, - vec![simple_completion_item( - "Something", - CompletionItemKind::STRUCT, - Some("Something".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_type_in_trait_function() { - let src = r#" - struct Something {} - - trait Trait { - fn foo(s: So>|<); - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "Something", - CompletionItemKind::STRUCT, - Some("Something".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_type_in_trait_function_return_type() { - let src = r#" - struct Something {} - - trait Trait { - fn foo() -> So>|<; - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "Something", - CompletionItemKind::STRUCT, - Some("Something".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_type_in_let_type() { - let src = r#" - struct Something {} - - fn main() { - let x: So>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "Something", - CompletionItemKind::STRUCT, - Some("Something".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_type_in_lambda_parameter() { - let src = r#" - struct Something {} - - fn main() { - foo(|s: So>|<| s) - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "Something", - CompletionItemKind::STRUCT, - Some("Something".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_builtin_types() { - let src = r#" - fn foo(x: i>|<) {} - "#; - assert_completion( - src, - vec![ - simple_completion_item("i8", CompletionItemKind::STRUCT, Some("i8".to_string())), - simple_completion_item("i16", CompletionItemKind::STRUCT, Some("i16".to_string())), - simple_completion_item("i32", CompletionItemKind::STRUCT, Some("i32".to_string())), - simple_completion_item("i64", CompletionItemKind::STRUCT, Some("i64".to_string())), - ], - ) - .await; - } - - #[test] - async fn test_suggest_true() { - let src = r#" - fn main() { - let x = t>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "true", - CompletionItemKind::KEYWORD, - Some("bool".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_regarding_if_scope() { - let src = r#" - fn main() { - let good = 1; - if true { - let great = 2; - g>|< - } else { - let greater = 3; - } - } - "#; - assert_completion( - src, - vec![ - simple_completion_item( - "good", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - ), - simple_completion_item( - "great", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - ), - ], - ) - .await; - - let src = r#" - fn main() { - let good = 1; - if true { - let great = 2; - } else { - let greater = 3; - g>|< - } - } - "#; - assert_completion( - src, - vec![ - simple_completion_item( - "good", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - ), - simple_completion_item( - "greater", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - ), - ], - ) - .await; - - let src = r#" - fn main() { - let good = 1; - if true { - let great = 2; - } else { - let greater = 3; - } - g>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "good", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_regarding_block_scope() { - let src = r#" - fn main() { - let good = 1; - { - let great = 2; - g>|< - } - } - "#; - assert_completion( - src, - vec![ - simple_completion_item( - "good", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - ), - simple_completion_item( - "great", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - ), - ], - ) - .await; - - let src = r#" - fn main() { - let good = 1; - { - let great = 2; - } - g>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "good", - CompletionItemKind::VARIABLE, - Some("Field".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggest_struct_type_parameter() { - let src = r#" - struct Foo { - context: C>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], - ) - .await; - } - - #[test] - async fn test_suggest_impl_type_parameter() { - let src = r#" - struct Foo {} - - impl Foo { - fn foo() { - let x: TypeP>|< - } - } - "#; - assert_completion( - src, - vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], - ) - .await; - } - - #[test] - async fn test_suggest_trait_impl_type_parameter() { - let src = r#" - struct Foo {} - trait Trait {} - - impl Trait for Foo { - fn foo() { - let x: TypeP>|< - } - } - "#; - assert_completion( - src, - vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], - ) - .await; - } - - #[test] - async fn test_suggest_trait_function_type_parameter() { - let src = r#" - struct Foo {} - trait Trait { - fn foo() { - let x: TypeP>|< - } - } - "#; - assert_completion( - src, - vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], - ) - .await; - } - - #[test] - async fn test_suggest_function_type_parameters() { - let src = r#" - fn foo(x: C>|<) {} - "#; - assert_completion( - src, - vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], - ) - .await; - } - - #[test] - async fn test_suggests_struct_field_after_dot_and_letter() { - let src = r#" - struct Some { - property: i32, - } - - fn foo(s: Some) { - s.p>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "property", - CompletionItemKind::FIELD, - Some("i32".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggests_struct_field_after_dot_and_letter_for_generic_type() { - let src = r#" - struct Some { - property: T, - } - - fn foo(s: Some) { - s.p>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "property", - CompletionItemKind::FIELD, - Some("i32".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggests_struct_field_after_dot_followed_by_brace() { - let src = r#" - struct Some { - property: i32, - } - - fn foo(s: Some) { - s.>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item( - "property", - CompletionItemKind::FIELD, - Some("i32".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggests_struct_field_after_dot_chain() { - let src = r#" - struct Some { - property: Other, - } - - struct Other { - bar: i32, - } - - fn foo(some: Some) { - some.property.>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item("bar", CompletionItemKind::FIELD, Some("i32".to_string()))], - ) - .await; - } - - #[test] - async fn test_suggests_struct_impl_method() { - let src = r#" - struct Some { - } - - impl Some { - fn foobar(self, x: i32) {} - fn foobar2(&mut self, x: i32) {} - fn foobar3(y: i32) {} - } - - fn foo(some: Some) { - some.f>|< - } - "#; - assert_completion( - src, - vec![ - snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:x})", - Some("fn(self, i32)".to_string()), - ), - snippet_completion_item( - "foobar2(…)", - CompletionItemKind::FUNCTION, - "foobar2(${1:x})", - Some("fn(&mut self, i32)".to_string()), - ), - ], - ) - .await; - } - - #[test] - async fn test_suggests_struct_trait_impl_method() { - let src = r#" - struct Some { - } - - trait SomeTrait { - fn foobar(self, x: i32); - fn foobar2(y: i32); - } - - impl SomeTrait for Some { - fn foobar(self, x: i32) {} - fn foobar2(y: i32) {} - } - - fn foo(some: Some) { - some.f>|< - } - "#; - assert_completion( - src, - vec![snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:x})", - Some("fn(self, i32)".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggests_primitive_trait_impl_method() { - let src = r#" - trait SomeTrait { - fn foobar(self, x: i32); - fn foobar2(y: i32); - } - - impl SomeTrait for Field { - fn foobar(self, x: i32) {} - fn foobar2(y: i32) {} - } - - fn foo(field: Field) { - field.f>|< - } - "#; - assert_completion( - src, - vec![snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:x})", - Some("fn(self, i32)".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggests_struct_methods_after_colons() { - let src = r#" - struct Some { - } - - impl Some { - fn foobar(self, x: i32) {} - fn foobar2(&mut self, x: i32) {} - fn foobar3(y: i32) {} - } - - fn foo() { - Some::>|< - } - "#; - assert_completion( - src, - vec![ - completion_item_with_sort_text( - snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:self}, ${2:x})", - Some("fn(self, i32)".to_string()), - ), - self_mismatch_sort_text(), - ), - completion_item_with_sort_text( - snippet_completion_item( - "foobar2(…)", - CompletionItemKind::FUNCTION, - "foobar2(${1:self}, ${2:x})", - Some("fn(&mut self, i32)".to_string()), - ), - self_mismatch_sort_text(), - ), - snippet_completion_item( - "foobar3(…)", - CompletionItemKind::FUNCTION, - "foobar3(${1:y})", - Some("fn(i32)".to_string()), - ), - ], - ) - .await; - } - - #[test] - async fn test_suggests_struct_behind_alias_methods_after_dot() { - let src = r#" - struct Some { - } - - type Alias = Some; - - impl Some { - fn foobar(self, x: i32) {} - } - - fn foo(some: Alias) { - some.>|< - } - "#; - assert_completion( - src, - vec![snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:x})", - Some("fn(self, i32)".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_suggests_struct_behind_alias_methods_after_colons() { - let src = r#" - struct Some { - } - - type Alias = Some; - - impl Some { - fn foobar(self, x: i32) {} - fn foobar2(&mut self, x: i32) {} - fn foobar3(y: i32) {} - } - - fn foo() { - Alias::>|< - } - "#; - assert_completion( - src, - vec![ - completion_item_with_sort_text( - snippet_completion_item( - "foobar(…)", - CompletionItemKind::FUNCTION, - "foobar(${1:self}, ${2:x})", - Some("fn(self, i32)".to_string()), - ), - self_mismatch_sort_text(), - ), - completion_item_with_sort_text( - snippet_completion_item( - "foobar2(…)", - CompletionItemKind::FUNCTION, - "foobar2(${1:self}, ${2:x})", - Some("fn(&mut self, i32)".to_string()), - ), - self_mismatch_sort_text(), - ), - snippet_completion_item( - "foobar3(…)", - CompletionItemKind::FUNCTION, - "foobar3(${1:y})", - Some("fn(i32)".to_string()), - ), - ], - ) - .await; - } - - #[test] - async fn test_completes_in_broken_if_after_dot() { - let src = r#" - struct Some { - foo: i32, - } - - fn foo(s: Some) { - if s.>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item("foo", CompletionItemKind::FIELD, Some("i32".to_string()))], - ) - .await; - } - - #[test] - async fn test_completes_in_nested_expression() { - let src = r#" - struct Foo { bar: Bar } - struct Bar { baz: i32 } - - fn foo(f: Foo) { - f.bar & f.>|< - } - "#; - assert_completion( - src, - vec![simple_completion_item("bar", CompletionItemKind::FIELD, Some("Bar".to_string()))], - ) - .await; - } - - #[test] - async fn test_completes_in_call_chain() { - let src = r#" - struct Foo {} - - impl Foo { - fn foo(self) -> Foo { self } - } - - fn foo(f: Foo) { - f.foo().>|< - } - "#; - assert_completion( - src, - vec![snippet_completion_item( - "foo()", - CompletionItemKind::FUNCTION, - "foo()", - Some("fn(self) -> Foo".to_string()), - )], - ) - .await; - } - - #[test] - async fn test_completes_when_assignment_follows() { - let src = r#" - struct Foo { - bar: i32, - } - - fn foo(f: Foo) { - let mut x = 1; - - f.>|< - - x = 2; - } - "#; - assert_completion( - src, - vec![simple_completion_item("bar", CompletionItemKind::FIELD, Some("i32".to_string()))], - ) - .await; - } -} diff --git a/tooling/lsp/src/requests/completion/builtins.rs b/tooling/lsp/src/requests/completion/builtins.rs index 7f88c9b3c73..75eba7fb3c7 100644 --- a/tooling/lsp/src/requests/completion/builtins.rs +++ b/tooling/lsp/src/requests/completion/builtins.rs @@ -1,4 +1,64 @@ +use lsp_types::CompletionItemKind; use noirc_frontend::token::Keyword; +use strum::IntoEnumIterator; + +use super::{ + completion_items::{simple_completion_item, snippet_completion_item}, + name_matches, NodeFinder, +}; + +impl<'a> NodeFinder<'a> { + pub(super) fn builtin_functions_completion(&mut self, prefix: &str) { + for keyword in Keyword::iter() { + if let Some(func) = keyword_builtin_function(&keyword) { + if name_matches(func.name, prefix) { + self.completion_items.push(snippet_completion_item( + format!("{}(…)", func.name), + CompletionItemKind::FUNCTION, + format!("{}({})", func.name, func.parameters), + Some(func.description.to_string()), + )); + } + } + } + } + + pub(super) fn builtin_values_completion(&mut self, prefix: &str) { + for keyword in ["false", "true"] { + if name_matches(keyword, prefix) { + self.completion_items.push(simple_completion_item( + keyword, + CompletionItemKind::KEYWORD, + Some("bool".to_string()), + )); + } + } + } + + pub(super) fn builtin_types_completion(&mut self, prefix: &str) { + for keyword in Keyword::iter() { + if let Some(typ) = keyword_builtin_type(&keyword) { + if name_matches(typ, prefix) { + self.completion_items.push(simple_completion_item( + typ, + CompletionItemKind::STRUCT, + Some(typ.to_string()), + )); + } + } + } + + for typ in builtin_integer_types() { + if name_matches(typ, prefix) { + self.completion_items.push(simple_completion_item( + typ, + CompletionItemKind::STRUCT, + Some(typ.to_string()), + )); + } + } + } +} pub(super) fn builtin_integer_types() -> [&'static str; 8] { ["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"] diff --git a/tooling/lsp/src/requests/completion/completion_items.rs b/tooling/lsp/src/requests/completion/completion_items.rs new file mode 100644 index 00000000000..db08dc9054a --- /dev/null +++ b/tooling/lsp/src/requests/completion/completion_items.rs @@ -0,0 +1,344 @@ +use lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat}; +use noirc_frontend::{ + hir_def::{function::FuncMeta, stmt::HirPattern}, + macros_api::{ModuleDefId, StructId}, + node_interner::{FuncId, GlobalId, TraitId, TypeAliasId}, + Type, +}; + +use super::{ + sort_text::{default_sort_text, new_sort_text, operator_sort_text, self_mismatch_sort_text}, + FunctionCompletionKind, FunctionKind, NodeFinder, RequestedItems, +}; + +impl<'a> NodeFinder<'a> { + pub(super) fn module_def_id_completion_item( + &self, + module_def_id: ModuleDefId, + name: String, + function_completion_kind: FunctionCompletionKind, + function_kind: FunctionKind, + requested_items: RequestedItems, + ) -> Option { + match requested_items { + RequestedItems::OnlyTypes => match module_def_id { + ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return None, + ModuleDefId::ModuleId(_) + | ModuleDefId::TypeId(_) + | ModuleDefId::TypeAliasId(_) + | ModuleDefId::TraitId(_) => (), + }, + RequestedItems::AnyItems => (), + } + + match module_def_id { + ModuleDefId::ModuleId(_) => Some(module_completion_item(name)), + ModuleDefId::FunctionId(func_id) => { + self.function_completion_item(func_id, function_completion_kind, function_kind) + } + ModuleDefId::TypeId(struct_id) => Some(self.struct_completion_item(struct_id)), + ModuleDefId::TypeAliasId(type_alias_id) => { + Some(self.type_alias_completion_item(type_alias_id)) + } + ModuleDefId::TraitId(trait_id) => Some(self.trait_completion_item(trait_id)), + ModuleDefId::GlobalId(global_id) => Some(self.global_completion_item(global_id)), + } + } + + fn struct_completion_item(&self, struct_id: StructId) -> CompletionItem { + let struct_type = self.interner.get_struct(struct_id); + let struct_type = struct_type.borrow(); + let name = struct_type.name.to_string(); + + simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)) + } + + fn type_alias_completion_item(&self, type_alias_id: TypeAliasId) -> CompletionItem { + let type_alias = self.interner.get_type_alias(type_alias_id); + let type_alias = type_alias.borrow(); + let name = type_alias.name.to_string(); + + simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name)) + } + + fn trait_completion_item(&self, trait_id: TraitId) -> CompletionItem { + let trait_ = self.interner.get_trait(trait_id); + let name = trait_.name.to_string(); + + simple_completion_item(name.clone(), CompletionItemKind::INTERFACE, Some(name)) + } + + fn global_completion_item(&self, global_id: GlobalId) -> CompletionItem { + let global_definition = self.interner.get_global_definition(global_id); + let name = global_definition.name.clone(); + + let global = self.interner.get_global(global_id); + let typ = self.interner.definition_type(global.definition_id); + let description = typ.to_string(); + + simple_completion_item(name, CompletionItemKind::CONSTANT, Some(description)) + } + + pub(super) fn function_completion_item( + &self, + func_id: FuncId, + function_completion_kind: FunctionCompletionKind, + function_kind: FunctionKind, + ) -> Option { + let func_meta = self.interner.function_meta(&func_id); + let name = &self.interner.function_name(&func_id).to_string(); + + let func_self_type = if let Some((pattern, typ, _)) = func_meta.parameters.0.get(0) { + if self.hir_pattern_is_self_type(pattern) { + if let Type::MutableReference(mut_typ) = typ { + let typ: &Type = mut_typ; + Some(typ) + } else { + Some(typ) + } + } else { + None + } + } else { + None + }; + + match function_kind { + FunctionKind::Any => (), + FunctionKind::SelfType(mut self_type) => { + if let Some(func_self_type) = func_self_type { + if matches!(self_type, Type::Integer(..)) + || matches!(self_type, Type::FieldElement) + { + // Check that the pattern type is the same as self type. + // We do this because some types (only Field and integer types) + // have their methods in the same HashMap. + + if let Type::MutableReference(mut_typ) = self_type { + self_type = mut_typ; + } + + if self_type != func_self_type { + return None; + } + } + } else { + return None; + } + } + } + + let is_operator = if let Some(trait_impl_id) = &func_meta.trait_impl { + let trait_impl = self.interner.get_trait_implementation(*trait_impl_id); + let trait_impl = trait_impl.borrow(); + self.interner.is_operator_trait(trait_impl.trait_id) + } else { + false + }; + let description = func_meta_type_to_string(func_meta, func_self_type.is_some()); + + let completion_item = match function_completion_kind { + FunctionCompletionKind::Name => { + simple_completion_item(name, CompletionItemKind::FUNCTION, Some(description)) + } + FunctionCompletionKind::NameAndParameters => { + let kind = CompletionItemKind::FUNCTION; + let insert_text = self.compute_function_insert_text(func_meta, name, function_kind); + let label = if insert_text.ends_with("()") { + format!("{}()", name) + } else { + format!("{}(…)", name) + }; + + snippet_completion_item(label, kind, insert_text, Some(description)) + } + }; + + let completion_item = if is_operator { + completion_item_with_sort_text(completion_item, operator_sort_text()) + } else if function_kind == FunctionKind::Any && name == "new" { + completion_item_with_sort_text(completion_item, new_sort_text()) + } else if function_kind == FunctionKind::Any && func_self_type.is_some() { + completion_item_with_sort_text(completion_item, self_mismatch_sort_text()) + } else { + completion_item + }; + + Some(completion_item) + } + + fn compute_function_insert_text( + &self, + func_meta: &FuncMeta, + name: &str, + function_kind: FunctionKind, + ) -> String { + let mut text = String::new(); + text.push_str(name); + text.push('('); + + let mut index = 1; + for (pattern, _, _) in &func_meta.parameters.0 { + if index == 1 { + match function_kind { + FunctionKind::SelfType(_) => { + if self.hir_pattern_is_self_type(pattern) { + continue; + } + } + FunctionKind::Any => (), + } + } + + if index > 1 { + text.push_str(", "); + } + + text.push_str("${"); + text.push_str(&index.to_string()); + text.push(':'); + self.hir_pattern_to_argument(pattern, &mut text); + text.push('}'); + + index += 1; + } + text.push(')'); + text + } + + fn hir_pattern_to_argument(&self, pattern: &HirPattern, text: &mut String) { + match pattern { + HirPattern::Identifier(hir_ident) => { + text.push_str(self.interner.definition_name(hir_ident.id)); + } + HirPattern::Mutable(pattern, _) => self.hir_pattern_to_argument(pattern, text), + HirPattern::Tuple(_, _) | HirPattern::Struct(_, _, _) => text.push('_'), + } + } + + fn hir_pattern_is_self_type(&self, pattern: &HirPattern) -> bool { + match pattern { + HirPattern::Identifier(hir_ident) => { + let name = self.interner.definition_name(hir_ident.id); + name == "self" || name == "_self" + } + HirPattern::Mutable(pattern, _) => self.hir_pattern_is_self_type(pattern), + HirPattern::Tuple(_, _) | HirPattern::Struct(_, _, _) => false, + } + } +} + +pub(super) fn module_completion_item(name: impl Into) -> CompletionItem { + simple_completion_item(name, CompletionItemKind::MODULE, None) +} + +pub(super) fn crate_completion_item(name: impl Into) -> CompletionItem { + simple_completion_item(name, CompletionItemKind::MODULE, None) +} + +fn func_meta_type_to_string(func_meta: &FuncMeta, has_self_type: bool) -> String { + let mut typ = &func_meta.typ; + if let Type::Forall(_, typ_) = typ { + typ = typ_; + } + + if let Type::Function(args, ret, _env, unconstrained) = typ { + let mut string = String::new(); + if *unconstrained { + string.push_str("unconstrained "); + } + string.push_str("fn("); + for (index, arg) in args.iter().enumerate() { + if index > 0 { + string.push_str(", "); + } + if index == 0 && has_self_type { + type_to_self_string(arg, &mut string); + } else { + string.push_str(&arg.to_string()); + } + } + string.push(')'); + + let ret: &Type = ret; + if let Type::Unit = ret { + // Nothing + } else { + string.push_str(" -> "); + string.push_str(&ret.to_string()); + } + string + } else { + typ.to_string() + } +} + +fn type_to_self_string(typ: &Type, string: &mut String) { + if let Type::MutableReference(..) = typ { + string.push_str("&mut self"); + } else { + string.push_str("self"); + } +} + +pub(super) fn simple_completion_item( + label: impl Into, + kind: CompletionItemKind, + description: Option, +) -> CompletionItem { + CompletionItem { + label: label.into(), + label_details: Some(CompletionItemLabelDetails { detail: None, description }), + kind: Some(kind), + detail: None, + documentation: None, + deprecated: None, + preselect: None, + sort_text: Some(default_sort_text()), + filter_text: None, + insert_text: None, + insert_text_format: None, + insert_text_mode: None, + text_edit: None, + additional_text_edits: None, + command: None, + commit_characters: None, + data: None, + tags: None, + } +} + +pub(super) fn snippet_completion_item( + label: impl Into, + kind: CompletionItemKind, + insert_text: impl Into, + description: Option, +) -> CompletionItem { + CompletionItem { + label: label.into(), + label_details: Some(CompletionItemLabelDetails { detail: None, description }), + kind: Some(kind), + insert_text_format: Some(InsertTextFormat::SNIPPET), + insert_text: Some(insert_text.into()), + detail: None, + documentation: None, + deprecated: None, + preselect: None, + sort_text: Some(default_sort_text()), + filter_text: None, + insert_text_mode: None, + text_edit: None, + additional_text_edits: None, + command: None, + commit_characters: None, + data: None, + tags: None, + } +} + +pub(super) fn completion_item_with_sort_text( + completion_item: CompletionItem, + sort_text: String, +) -> CompletionItem { + CompletionItem { sort_text: Some(sort_text), ..completion_item } +} diff --git a/tooling/lsp/src/requests/completion/kinds.rs b/tooling/lsp/src/requests/completion/kinds.rs new file mode 100644 index 00000000000..e01fcfc8c56 --- /dev/null +++ b/tooling/lsp/src/requests/completion/kinds.rs @@ -0,0 +1,42 @@ +use noirc_frontend::Type; + +/// When finding items in a module, whether to show only direct children or all visible items. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum ModuleCompletionKind { + // Only show a module's direct children. This is used when completing a use statement + // or a path after the first segment. + DirectChildren, + // Show all of a module's visible items. This is used when completing a path outside + // of a use statement (in regular code) when the path is just a single segment: + // we want to find items exposed in the current module. + AllVisibleItems, +} + +/// When suggest a function as a result of completion, whether to autocomplete its name or its name and parameters. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum FunctionCompletionKind { + // Only complete a function's name. This is used in use statement. + Name, + // Complete a function's name and parameters (as a snippet). This is used in regular code. + NameAndParameters, +} + +/// Is there a requirement for suggesting functions? +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum FunctionKind<'a> { + /// No requirement: any function is okay to suggest. + Any, + /// Only show functions that have the given self type. + SelfType(&'a Type), +} + +/// When requesting completions, whether to list all items or just types. +/// For example, when writing `let x: S` we only want to suggest types at this +/// point (modules too, because they might include types too). +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum RequestedItems { + // Suggest any items (types, functions, etc.). + AnyItems, + // Only suggest types. + OnlyTypes, +} diff --git a/tooling/lsp/src/requests/completion/sort_text.rs b/tooling/lsp/src/requests/completion/sort_text.rs new file mode 100644 index 00000000000..4a8aebd7988 --- /dev/null +++ b/tooling/lsp/src/requests/completion/sort_text.rs @@ -0,0 +1,28 @@ +/// Sort text for "new" methods: we want these to show up before anything else, +/// if we are completing at something like `Foo::` +pub(super) fn new_sort_text() -> String { + "3".to_string() +} + +/// This is the default sort text. +pub(super) fn default_sort_text() -> String { + "5".to_string() +} + +/// When completing something like `Foo::`, we want to show methods that take +/// self after the other ones. +pub(super) fn self_mismatch_sort_text() -> String { + "7".to_string() +} + +/// We want to show operator methods last. +pub(super) fn operator_sort_text() -> String { + "8".to_string() +} + +/// If a name begins with underscore it's likely something that's meant to +/// be private (but visibility doesn't exist everywhere yet, so for now +/// we assume that) +pub(super) fn underscore_sort_text() -> String { + "9".to_string() +} diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs new file mode 100644 index 00000000000..1c89cc09413 --- /dev/null +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -0,0 +1,1351 @@ +#[cfg(test)] +mod completion_tests { + use crate::{ + notifications::on_did_open_text_document, + requests::{ + completion::{ + completion_items::{ + completion_item_with_sort_text, crate_completion_item, module_completion_item, + simple_completion_item, snippet_completion_item, + }, + sort_text::self_mismatch_sort_text, + }, + on_completion_request, + }, + test_utils, + }; + + use lsp_types::{ + CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, + DidOpenTextDocumentParams, PartialResultParams, Position, TextDocumentIdentifier, + TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams, + }; + use tokio::test; + + async fn assert_completion(src: &str, expected: Vec) { + let (mut state, noir_text_document) = test_utils::init_lsp_server("document_symbol").await; + + let (line, column) = src + .lines() + .enumerate() + .filter_map(|(line_index, line)| { + line.find(">|<").map(|char_index| (line_index, char_index)) + }) + .next() + .expect("Expected to find one >|< in the source code"); + + let src = src.replace(">|<", ""); + + on_did_open_text_document( + &mut state, + DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: noir_text_document.clone(), + language_id: "noir".to_string(), + version: 0, + text: src.to_string(), + }, + }, + ); + + // Get inlay hints. These should now be relative to the changed text, + // not the saved file's text. + let response = on_completion_request( + &mut state, + CompletionParams { + text_document_position: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: noir_text_document }, + position: Position { line: line as u32, character: column as u32 }, + }, + work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, + partial_result_params: PartialResultParams { partial_result_token: None }, + context: None, + }, + ) + .await + .expect("Could not execute on_completion_request") + .unwrap(); + + let CompletionResponse::Array(items) = response else { + panic!("Expected response to be CompletionResponse::Array"); + }; + + let mut items = items.clone(); + items.sort_by_key(|item| item.label.clone()); + + let mut expected = expected.clone(); + expected.sort_by_key(|item| item.label.clone()); + + if items != expected { + println!( + "Items: {:?}", + items.iter().map(|item| item.label.clone()).collect::>() + ); + println!( + "Expected: {:?}", + expected.iter().map(|item| item.label.clone()).collect::>() + ); + } + + assert_eq!(items, expected); + } + + #[test] + async fn test_use_first_segment() { + let src = r#" + mod foo {} + mod foobar {} + use f>|< + "#; + + assert_completion( + src, + vec![module_completion_item("foo"), module_completion_item("foobar")], + ) + .await; + } + + #[test] + async fn test_use_second_segment() { + let src = r#" + mod foo { + mod bar {} + mod baz {} + } + use foo::>|< + "#; + + assert_completion(src, vec![module_completion_item("bar"), module_completion_item("baz")]) + .await; + } + + #[test] + async fn test_use_second_segment_after_typing() { + let src = r#" + mod foo { + mod bar {} + mod brave {} + } + use foo::ba>|< + "#; + + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_use_struct() { + let src = r#" + mod foo { + struct Foo {} + } + use foo::>|< + "#; + + assert_completion( + src, + vec![simple_completion_item( + "Foo", + CompletionItemKind::STRUCT, + Some("Foo".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_use_function() { + let src = r#" + mod foo { + fn bar(x: i32) -> u64 { 0 } + } + use foo::>|< + "#; + + assert_completion( + src, + vec![simple_completion_item( + "bar", + CompletionItemKind::FUNCTION, + Some("fn(i32) -> u64".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_use_after_crate_and_letter() { + // Prove that "std" shows up + let src = r#" + use s>|< + "#; + assert_completion(src, vec![crate_completion_item("std")]).await; + + // "std" doesn't show up anymore because of the "crate::" prefix + let src = r#" + mod something {} + use crate::s>|< + "#; + assert_completion(src, vec![module_completion_item("something")]).await; + } + + #[test] + async fn test_use_suggests_hardcoded_crate() { + let src = r#" + use c>|< + "#; + + assert_completion( + src, + vec![simple_completion_item("crate::", CompletionItemKind::KEYWORD, None)], + ) + .await; + } + + #[test] + async fn test_use_in_tree_after_letter() { + let src = r#" + mod foo { + mod bar {} + } + use foo::{b>|<} + "#; + + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_use_in_tree_after_colons() { + let src = r#" + mod foo { + mod bar { + mod baz {} + } + } + use foo::{bar::>|<} + "#; + + assert_completion(src, vec![module_completion_item("baz")]).await; + } + + #[test] + async fn test_use_in_tree_after_colons_after_another_segment() { + let src = r#" + mod foo { + mod bar {} + mod qux {} + } + use foo::{bar, q>|<} + "#; + + assert_completion(src, vec![module_completion_item("qux")]).await; + } + + #[test] + async fn test_use_in_nested_module() { + let src = r#" + mod foo { + mod something {} + + use s>|< + } + "#; + + assert_completion( + src, + vec![ + module_completion_item("something"), + crate_completion_item("std"), + simple_completion_item("super::", CompletionItemKind::KEYWORD, None), + ], + ) + .await; + } + + #[test] + async fn test_use_after_super() { + let src = r#" + mod foo {} + + mod bar { + mod something {} + + use super::f>|< + } + "#; + + assert_completion(src, vec![module_completion_item("foo")]).await; + } + + #[test] + async fn test_use_after_crate_and_letter_nested_in_module() { + let src = r#" + mod something { + mod something_else {} + use crate::s>|< + } + + "#; + assert_completion(src, vec![module_completion_item("something")]).await; + } + + #[test] + async fn test_use_after_crate_segment_and_letter_nested_in_module() { + let src = r#" + mod something { + mod something_else {} + use crate::something::s>|< + } + + "#; + assert_completion(src, vec![module_completion_item("something_else")]).await; + } + + #[test] + async fn test_complete_path_shows_module() { + let src = r#" + mod foobar {} + + fn main() { + fo>|< + } + "#; + assert_completion(src, vec![module_completion_item("foobar")]).await; + } + + #[test] + async fn test_complete_path_after_colons_shows_submodule() { + let src = r#" + mod foo { + mod bar {} + } + + fn main() { + foo::>|< + } + "#; + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_complete_path_after_colons_and_letter_shows_submodule() { + let src = r#" + mod foo { + mod bar {} + } + + fn main() { + foo::b>|< + } + "#; + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_complete_path_with_local_variable() { + let src = r#" + fn main() { + let local = 1; + l>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_shadowed_local_variable() { + let src = r#" + fn main() { + let local = 1; + let local = true; + l>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("bool".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_function_argument() { + let src = r#" + fn main(local: Field) { + l>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_function_without_arguments() { + let src = r#" + fn hello() { } + + fn main() { + h>|< + } + "#; + assert_completion( + src, + vec![snippet_completion_item( + "hello()", + CompletionItemKind::FUNCTION, + "hello()", + Some("fn()".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_function() { + let src = r#" + fn hello(x: i32, y: Field) { } + + fn main() { + h>|< + } + "#; + assert_completion( + src, + vec![snippet_completion_item( + "hello(…)", + CompletionItemKind::FUNCTION, + "hello(${1:x}, ${2:y})", + Some("fn(i32, Field)".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_builtin_functions() { + let src = r#" + fn main() { + a>|< + } + "#; + assert_completion( + src, + vec![ + snippet_completion_item( + "assert(…)", + CompletionItemKind::FUNCTION, + "assert(${1:predicate})", + Some("fn(T)".to_string()), + ), + snippet_completion_item( + "assert_constant(…)", + CompletionItemKind::FUNCTION, + "assert_constant(${1:x})", + Some("fn(T)".to_string()), + ), + snippet_completion_item( + "assert_eq(…)", + CompletionItemKind::FUNCTION, + "assert_eq(${1:lhs}, ${2:rhs})", + Some("fn(T, T)".to_string()), + ), + ], + ) + .await; + } + + #[test] + async fn test_complete_path_in_impl() { + let src = r#" + struct SomeStruct {} + + impl SomeStruct { + fn foo() { + S>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "SomeStruct", + CompletionItemKind::STRUCT, + Some("SomeStruct".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_in_trait_impl() { + let src = r#" + struct SomeStruct {} + trait Trait {} + + impl Trait for SomeStruct { + fn foo() { + S>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "SomeStruct", + CompletionItemKind::STRUCT, + Some("SomeStruct".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_for_argument() { + let src = r#" + fn main() { + for index in 0..10 { + i>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "index", + CompletionItemKind::VARIABLE, + Some("u32".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_lambda_argument() { + let src = r#" + fn lambda(f: fn(i32)) { } + + fn main() { + lambda(|var| v>|<) + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "var", + CompletionItemKind::VARIABLE, + Some("_".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_struct_field_type() { + let src = r#" + struct Something {} + + fn SomeFunction() {} + + struct Another { + some: So>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_function_parameter() { + let src = r#" + struct Something {} + + fn foo(x: So>|<) {} + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_function_return_type() { + let src = r#" + struct Something {} + + fn foo() -> So>|< {} + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_type_alias() { + let src = r#" + struct Something {} + + type Foo = So>|< + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_trait_function() { + let src = r#" + struct Something {} + + trait Trait { + fn foo(s: So>|<); + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_trait_function_return_type() { + let src = r#" + struct Something {} + + trait Trait { + fn foo() -> So>|<; + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_let_type() { + let src = r#" + struct Something {} + + fn main() { + let x: So>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_lambda_parameter() { + let src = r#" + struct Something {} + + fn main() { + foo(|s: So>|<| s) + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_builtin_types() { + let src = r#" + fn foo(x: i>|<) {} + "#; + assert_completion( + src, + vec![ + simple_completion_item("i8", CompletionItemKind::STRUCT, Some("i8".to_string())), + simple_completion_item("i16", CompletionItemKind::STRUCT, Some("i16".to_string())), + simple_completion_item("i32", CompletionItemKind::STRUCT, Some("i32".to_string())), + simple_completion_item("i64", CompletionItemKind::STRUCT, Some("i64".to_string())), + ], + ) + .await; + } + + #[test] + async fn test_suggest_true() { + let src = r#" + fn main() { + let x = t>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "true", + CompletionItemKind::KEYWORD, + Some("bool".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_regarding_if_scope() { + let src = r#" + fn main() { + let good = 1; + if true { + let great = 2; + g>|< + } else { + let greater = 3; + } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + simple_completion_item( + "great", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + ], + ) + .await; + + let src = r#" + fn main() { + let good = 1; + if true { + let great = 2; + } else { + let greater = 3; + g>|< + } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + simple_completion_item( + "greater", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + ], + ) + .await; + + let src = r#" + fn main() { + let good = 1; + if true { + let great = 2; + } else { + let greater = 3; + } + g>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_regarding_block_scope() { + let src = r#" + fn main() { + let good = 1; + { + let great = 2; + g>|< + } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + simple_completion_item( + "great", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + ], + ) + .await; + + let src = r#" + fn main() { + let good = 1; + { + let great = 2; + } + g>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_struct_type_parameter() { + let src = r#" + struct Foo { + context: C>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_impl_type_parameter() { + let src = r#" + struct Foo {} + + impl Foo { + fn foo() { + let x: TypeP>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_trait_impl_type_parameter() { + let src = r#" + struct Foo {} + trait Trait {} + + impl Trait for Foo { + fn foo() { + let x: TypeP>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_trait_function_type_parameter() { + let src = r#" + struct Foo {} + trait Trait { + fn foo() { + let x: TypeP>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_function_type_parameters() { + let src = r#" + fn foo(x: C>|<) {} + "#; + assert_completion( + src, + vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggests_struct_field_after_dot_and_letter() { + let src = r#" + struct Some { + property: i32, + } + + fn foo(s: Some) { + s.p>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "property", + CompletionItemKind::FIELD, + Some("i32".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggests_struct_field_after_dot_and_letter_for_generic_type() { + let src = r#" + struct Some { + property: T, + } + + fn foo(s: Some) { + s.p>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "property", + CompletionItemKind::FIELD, + Some("i32".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggests_struct_field_after_dot_followed_by_brace() { + let src = r#" + struct Some { + property: i32, + } + + fn foo(s: Some) { + s.>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "property", + CompletionItemKind::FIELD, + Some("i32".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggests_struct_field_after_dot_chain() { + let src = r#" + struct Some { + property: Other, + } + + struct Other { + bar: i32, + } + + fn foo(some: Some) { + some.property.>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item("bar", CompletionItemKind::FIELD, Some("i32".to_string()))], + ) + .await; + } + + #[test] + async fn test_suggests_struct_impl_method() { + let src = r#" + struct Some { + } + + impl Some { + fn foobar(self, x: i32) {} + fn foobar2(&mut self, x: i32) {} + fn foobar3(y: i32) {} + } + + fn foo(some: Some) { + some.f>|< + } + "#; + assert_completion( + src, + vec![ + snippet_completion_item( + "foobar(…)", + CompletionItemKind::FUNCTION, + "foobar(${1:x})", + Some("fn(self, i32)".to_string()), + ), + snippet_completion_item( + "foobar2(…)", + CompletionItemKind::FUNCTION, + "foobar2(${1:x})", + Some("fn(&mut self, i32)".to_string()), + ), + ], + ) + .await; + } + + #[test] + async fn test_suggests_struct_trait_impl_method() { + let src = r#" + struct Some { + } + + trait SomeTrait { + fn foobar(self, x: i32); + fn foobar2(y: i32); + } + + impl SomeTrait for Some { + fn foobar(self, x: i32) {} + fn foobar2(y: i32) {} + } + + fn foo(some: Some) { + some.f>|< + } + "#; + assert_completion( + src, + vec![snippet_completion_item( + "foobar(…)", + CompletionItemKind::FUNCTION, + "foobar(${1:x})", + Some("fn(self, i32)".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggests_primitive_trait_impl_method() { + let src = r#" + trait SomeTrait { + fn foobar(self, x: i32); + fn foobar2(y: i32); + } + + impl SomeTrait for Field { + fn foobar(self, x: i32) {} + fn foobar2(y: i32) {} + } + + fn foo(field: Field) { + field.f>|< + } + "#; + assert_completion( + src, + vec![snippet_completion_item( + "foobar(…)", + CompletionItemKind::FUNCTION, + "foobar(${1:x})", + Some("fn(self, i32)".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggests_struct_methods_after_colons() { + let src = r#" + struct Some { + } + + impl Some { + fn foobar(self, x: i32) {} + fn foobar2(&mut self, x: i32) {} + fn foobar3(y: i32) {} + } + + fn foo() { + Some::>|< + } + "#; + assert_completion( + src, + vec![ + completion_item_with_sort_text( + snippet_completion_item( + "foobar(…)", + CompletionItemKind::FUNCTION, + "foobar(${1:self}, ${2:x})", + Some("fn(self, i32)".to_string()), + ), + self_mismatch_sort_text(), + ), + completion_item_with_sort_text( + snippet_completion_item( + "foobar2(…)", + CompletionItemKind::FUNCTION, + "foobar2(${1:self}, ${2:x})", + Some("fn(&mut self, i32)".to_string()), + ), + self_mismatch_sort_text(), + ), + snippet_completion_item( + "foobar3(…)", + CompletionItemKind::FUNCTION, + "foobar3(${1:y})", + Some("fn(i32)".to_string()), + ), + ], + ) + .await; + } + + #[test] + async fn test_suggests_struct_behind_alias_methods_after_dot() { + let src = r#" + struct Some { + } + + type Alias = Some; + + impl Some { + fn foobar(self, x: i32) {} + } + + fn foo(some: Alias) { + some.>|< + } + "#; + assert_completion( + src, + vec![snippet_completion_item( + "foobar(…)", + CompletionItemKind::FUNCTION, + "foobar(${1:x})", + Some("fn(self, i32)".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggests_struct_behind_alias_methods_after_colons() { + let src = r#" + struct Some { + } + + type Alias = Some; + + impl Some { + fn foobar(self, x: i32) {} + fn foobar2(&mut self, x: i32) {} + fn foobar3(y: i32) {} + } + + fn foo() { + Alias::>|< + } + "#; + assert_completion( + src, + vec![ + completion_item_with_sort_text( + snippet_completion_item( + "foobar(…)", + CompletionItemKind::FUNCTION, + "foobar(${1:self}, ${2:x})", + Some("fn(self, i32)".to_string()), + ), + self_mismatch_sort_text(), + ), + completion_item_with_sort_text( + snippet_completion_item( + "foobar2(…)", + CompletionItemKind::FUNCTION, + "foobar2(${1:self}, ${2:x})", + Some("fn(&mut self, i32)".to_string()), + ), + self_mismatch_sort_text(), + ), + snippet_completion_item( + "foobar3(…)", + CompletionItemKind::FUNCTION, + "foobar3(${1:y})", + Some("fn(i32)".to_string()), + ), + ], + ) + .await; + } + + #[test] + async fn test_completes_in_broken_if_after_dot() { + let src = r#" + struct Some { + foo: i32, + } + + fn foo(s: Some) { + if s.>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item("foo", CompletionItemKind::FIELD, Some("i32".to_string()))], + ) + .await; + } + + #[test] + async fn test_completes_in_nested_expression() { + let src = r#" + struct Foo { bar: Bar } + struct Bar { baz: i32 } + + fn foo(f: Foo) { + f.bar & f.>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item("bar", CompletionItemKind::FIELD, Some("Bar".to_string()))], + ) + .await; + } + + #[test] + async fn test_completes_in_call_chain() { + let src = r#" + struct Foo {} + + impl Foo { + fn foo(self) -> Foo { self } + } + + fn foo(f: Foo) { + f.foo().>|< + } + "#; + assert_completion( + src, + vec![snippet_completion_item( + "foo()", + CompletionItemKind::FUNCTION, + "foo()", + Some("fn(self) -> Foo".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_completes_when_assignment_follows() { + let src = r#" + struct Foo { + bar: i32, + } + + fn foo(f: Foo) { + let mut x = 1; + + f.>|< + + x = 2; + } + "#; + assert_completion( + src, + vec![simple_completion_item("bar", CompletionItemKind::FIELD, Some("i32".to_string()))], + ) + .await; + } +}