From 8a82d54f044cd10c9dbe8ee90414942ff4db98ee Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 10:04:28 -0300 Subject: [PATCH 01/37] Start autocompleting paths --- .../src/hir/def_map/module_data.rs | 2 +- tooling/lsp/src/requests/completion.rs | 298 +++++++++++++++++- 2 files changed, 286 insertions(+), 14 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 22875ffe18a..8a0125cfe95 100644 --- a/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -38,7 +38,7 @@ impl ModuleData { } } - pub(crate) fn scope(&self) -> &ItemScope { + pub fn scope(&self) -> &ItemScope { &self.scope } diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 0c1f7e724dc..8d743f9ea48 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -11,7 +11,10 @@ use lsp_types::{ }; use noirc_errors::Span; use noirc_frontend::{ - ast::{Ident, Path, PathKind, PathSegment, UseTree, UseTreeKind}, + ast::{ + BlockExpression, Expression, Ident, LetStatement, NoirFunction, Path, PathKind, + PathSegment, Statement, UseTree, UseTreeKind, + }, graph::{CrateId, Dependency}, hir::{ def_map::{CrateDefMap, LocalModuleId, ModuleId}, @@ -27,6 +30,12 @@ use crate::{utils, LspState}; use super::process_request; +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum ModuleCompletionKind { + DirectChildren, + AllVisibleItems, +} + pub(crate) fn on_completion_request( state: &mut LspState, params: CompletionParams, @@ -122,9 +131,7 @@ impl<'a> NodeFinder<'a> { match &item.kind { ItemKind::Import(use_tree) => { let mut prefixes = Vec::new(); - if let Some(completion) = self.find_in_use_tree(use_tree, &mut prefixes) { - return Some(completion); - } + self.find_in_use_tree(use_tree, &mut prefixes) } ItemKind::Submodules(parsed_sub_module) => { // Switch `self.module_id` to the submodule @@ -142,16 +149,215 @@ impl<'a> NodeFinder<'a> { // Restore the old module before continuing self.module_id = previous_module_id; - if let Some(completion) = completion { - return Some(completion); - } + completion + } + ItemKind::Function(noir_function) => self.find_in_noir_function(noir_function), + _ => { + // TODO + None } - _ => (), } + } + fn find_in_noir_function( + &mut self, + noir_function: &NoirFunction, + ) -> Option { + self.find_in_block_expression(&noir_function.def.body) + } + + fn find_in_block_expression( + &mut self, + block_expression: &BlockExpression, + ) -> Option { + for statement in &block_expression.statements { + if let Some(completion) = self.find_in_statement(statement) { + return Some(completion); + } + } None } + fn find_in_statement(&mut self, statement: &Statement) -> Option { + if !self.includes_span(statement.span) { + return None; + } + + match &statement.kind { + noirc_frontend::ast::StatementKind::Let(let_statement) => { + self.find_in_let_statement(let_statement) + } + noirc_frontend::ast::StatementKind::Constrain(_) => { + // TODO + None + } + noirc_frontend::ast::StatementKind::Expression(expression) => { + self.find_in_expression(expression) + } + noirc_frontend::ast::StatementKind::Assign(_) => { + // TODO + None + } + noirc_frontend::ast::StatementKind::For(_) => { + // TODO + None + } + noirc_frontend::ast::StatementKind::Break => { + // TODO + None + } + noirc_frontend::ast::StatementKind::Continue => { + // TODO + None + } + noirc_frontend::ast::StatementKind::Comptime(_) => { + // TODO + None + } + noirc_frontend::ast::StatementKind::Semi(_) => { + // TODO + None + } + noirc_frontend::ast::StatementKind::Error => { + // TODO + None + } + } + } + + fn find_in_let_statement( + &mut self, + let_statement: &LetStatement, + ) -> Option { + self.find_in_expression(&let_statement.expression) + } + + fn find_in_expression(&mut self, expression: &Expression) -> Option { + if !self.includes_span(expression.span) { + return None; + } + + match &expression.kind { + noirc_frontend::ast::ExpressionKind::Literal(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Block(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Prefix(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Index(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Call(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::MethodCall(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Constructor(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::MemberAccess(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Cast(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Infix(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::If(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Variable(path) => self.find_in_path(path), + noirc_frontend::ast::ExpressionKind::Tuple(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Lambda(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Parenthesized(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Quote(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Unquote(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Comptime(_, _) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::AsTraitPath(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Resolved(_) => { + // TODO + None + } + noirc_frontend::ast::ExpressionKind::Error => { + // TODO + None + } + } + } + + fn find_in_path(&mut self, path: &Path) -> Option { + // Only offer completions if we are right at the end of the path + if self.byte_index != path.span.end() as usize { + return None; + } + + let after_colons = self.byte == Some(b':'); + + let mut idents: Vec = + path.segments.iter().map(|segment| segment.ident.clone()).collect(); + let prefix; + let at_root; + + if after_colons { + prefix = String::new(); + at_root = false; + } else { + prefix = idents.pop().unwrap().to_string(); + at_root = idents.is_empty(); + } + + let module_id = + if idents.is_empty() { Some(self.module_id) } else { self.resolve_module(idents) }; + let Some(module_id) = module_id else { + return None; + }; + + let module_completion_kind = if after_colons { + ModuleCompletionKind::DirectChildren + } else { + ModuleCompletionKind::AllVisibleItems + }; + + self.complete_in_module(module_id, prefix, path.kind, at_root, module_completion_kind) + } + fn find_in_use_tree( &self, use_tree: &UseTree, @@ -206,6 +412,8 @@ impl<'a> NodeFinder<'a> { } } + let module_completion_kind = ModuleCompletionKind::DirectChildren; + if after_colons { // We are right after "::" segments.push(ident.clone()); @@ -213,18 +421,36 @@ impl<'a> NodeFinder<'a> { self.resolve_module(segments).and_then(|module_id| { let prefix = String::new(); let at_root = false; - self.complete_in_module(module_id, prefix, path_kind, at_root) + self.complete_in_module( + module_id, + prefix, + path_kind, + at_root, + module_completion_kind, + ) }) } else { // We are right after the last segment let prefix = ident.to_string(); if segments.is_empty() { let at_root = true; - self.complete_in_module(self.module_id, prefix, path_kind, at_root) + self.complete_in_module( + self.module_id, + prefix, + path_kind, + at_root, + module_completion_kind, + ) } else { let at_root = false; self.resolve_module(segments).and_then(|module_id| { - self.complete_in_module(module_id, prefix, path_kind, at_root) + self.complete_in_module( + module_id, + prefix, + path_kind, + at_root, + module_completion_kind, + ) }) } } @@ -236,6 +462,7 @@ impl<'a> NodeFinder<'a> { prefix: String, path_kind: PathKind, at_root: bool, + module_completion_kind: ModuleCompletionKind, ) -> Option { let def_map = &self.def_maps[&module_id.krate]; let mut module_data = def_map.modules().get(module_id.local_id.0)?; @@ -255,7 +482,12 @@ impl<'a> NodeFinder<'a> { let mut completion_items = Vec::new(); - for ident in module_data.definitions().names() { + let items = match module_completion_kind { + ModuleCompletionKind::DirectChildren => module_data.definitions(), + ModuleCompletionKind::AllVisibleItems => module_data.scope(), + }; + + for ident in items.names() { let name = &ident.0.contents; if name_matches(name, &prefix) { @@ -701,8 +933,8 @@ mod completion_tests { "#; assert_completion(src, vec![module_completion_item("something")]).await; } - #[test] + #[test] async fn test_use_after_crate_segment_and_letter_nested_in_module() { let src = r#" mod something { @@ -713,4 +945,44 @@ mod completion_tests { "#; assert_completion(src, vec![module_completion_item("something_else")]).await; } + + #[test] + async fn test_complete_path_shows_module() { + let src = r#" + mod foo {} + + fn main() { + f>|< + } + "#; + assert_completion(src, vec![module_completion_item("foo")]).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; + } } From 136d8829f57a24ab62d006bd94a301f7d4d125bb Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 10:34:49 -0300 Subject: [PATCH 02/37] Autocomplete with local variables too --- tooling/lsp/src/requests/completion.rs | 157 ++++++++++++++++++++++--- 1 file changed, 141 insertions(+), 16 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 8d743f9ea48..656f436a8ab 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -1,5 +1,5 @@ use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, future::{self, Future}, }; @@ -9,11 +9,11 @@ use lsp_types::{ CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionParams, CompletionResponse, }; -use noirc_errors::Span; +use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ BlockExpression, Expression, Ident, LetStatement, NoirFunction, Path, PathKind, - PathSegment, Statement, UseTree, UseTreeKind, + PathSegment, Pattern, Statement, UseTree, UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ @@ -21,7 +21,7 @@ use noirc_frontend::{ resolution::path_resolver::{PathResolver, StandardPathResolver}, }, macros_api::{ModuleDefId, NodeInterner, StructId}, - node_interner::{FuncId, GlobalId, TraitId, TypeAliasId}, + node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, parser::{Item, ItemKind}, ParsedModule, Type, }; @@ -73,6 +73,7 @@ pub(crate) fn on_completion_request( } struct NodeFinder<'a> { + file: FileId, byte_index: usize, byte: Option, root_module_id: ModuleId, @@ -80,6 +81,7 @@ struct NodeFinder<'a> { def_maps: &'a BTreeMap, dependencies: &'a Vec, interner: &'a NodeInterner, + local_variables: HashMap, } impl<'a> NodeFinder<'a> { @@ -103,7 +105,18 @@ impl<'a> NodeFinder<'a> { def_map.root() }; let module_id = ModuleId { krate, local_id }; - Self { byte_index, byte, root_module_id, module_id, def_maps, dependencies, interner } + let local_variables = HashMap::new(); + Self { + file, + byte_index, + byte, + root_module_id, + module_id, + def_maps, + dependencies, + interner, + local_variables, + } } fn find(&mut self, parsed_module: &ParsedModule) -> Option { @@ -163,6 +176,7 @@ impl<'a> NodeFinder<'a> { &mut self, noir_function: &NoirFunction, ) -> Option { + self.local_variables.clear(); self.find_in_block_expression(&noir_function.def.body) } @@ -179,10 +193,6 @@ impl<'a> NodeFinder<'a> { } fn find_in_statement(&mut self, statement: &Statement) -> Option { - if !self.includes_span(statement.span) { - return None; - } - match &statement.kind { noirc_frontend::ast::StatementKind::Let(let_statement) => { self.find_in_let_statement(let_statement) @@ -229,14 +239,16 @@ impl<'a> NodeFinder<'a> { &mut self, let_statement: &LetStatement, ) -> Option { - self.find_in_expression(&let_statement.expression) + if let Some(response) = self.find_in_expression(&let_statement.expression) { + return Some(response); + } + + self.collect_local_variables(&let_statement.pattern); + + None } fn find_in_expression(&mut self, expression: &Expression) -> Option { - if !self.includes_span(expression.span) { - return None; - } - match &expression.kind { noirc_frontend::ast::ExpressionKind::Literal(_) => { // TODO @@ -343,6 +355,8 @@ impl<'a> NodeFinder<'a> { at_root = idents.is_empty(); } + let is_single_segment = !after_colons && idents.is_empty() && path.kind == PathKind::Plain; + let module_id = if idents.is_empty() { Some(self.module_id) } else { self.resolve_module(idents) }; let Some(module_id) = module_id else { @@ -355,7 +369,50 @@ impl<'a> NodeFinder<'a> { ModuleCompletionKind::AllVisibleItems }; - self.complete_in_module(module_id, prefix, path.kind, at_root, module_completion_kind) + let response = self.complete_in_module( + module_id, + prefix.clone(), + path.kind, + at_root, + module_completion_kind, + ); + + if is_single_segment { + let local_vars_response = self.local_variables_completion(prefix); + merge_completion_responses(response, local_vars_response) + } else { + response + } + } + + fn local_variables_completion(&self, prefix: String) -> Option { + let mut completion_items = Vec::new(); + + for (name, span) in &self.local_variables { + if name_matches(name, &prefix) { + let location = Location::new(*span, self.file); + let description = if let Some(ReferenceId::Local(definition_id)) = + self.interner.reference_at_location(location) + { + let typ = self.interner.definition_type(definition_id); + Some(typ.to_string()) + } else { + None + }; + + completion_items.push(simple_completion_item( + name, + CompletionItemKind::VARIABLE, + description, + )); + } + } + + if completion_items.is_empty() { + None + } else { + Some(CompletionResponse::Array(completion_items)) + } } fn find_in_use_tree( @@ -456,6 +513,17 @@ impl<'a> NodeFinder<'a> { } } + fn collect_local_variables(&mut self, pattern: &Pattern) { + match pattern { + Pattern::Identifier(ident) => { + self.local_variables.insert(ident.to_string(), ident.span()); + } + Pattern::Mutable(_, _, _) => todo!(), + Pattern::Tuple(_, _) => todo!(), + Pattern::Struct(_, _, _) => todo!(), + } + } + fn complete_in_module( &self, module_id: ModuleId, @@ -529,7 +597,11 @@ impl<'a> NodeFinder<'a> { } } - Some(CompletionResponse::Array(completion_items)) + if completion_items.is_empty() { + None + } else { + Some(CompletionResponse::Array(completion_items)) + } } fn module_def_id_completion_item( @@ -657,6 +729,20 @@ fn simple_completion_item( } } +fn merge_completion_responses( + response1: Option, + response2: Option, +) -> Option { + match (response1, response2) { + (Some(CompletionResponse::Array(mut items1)), Some(CompletionResponse::Array(items2))) => { + items1.extend(items2); + Some(CompletionResponse::Array(items1)) + } + (Some(response), None) | (None, Some(response)) => Some(response), + _ => None, + } +} + #[cfg(test)] mod completion_tests { use crate::{notifications::on_did_open_text_document, test_utils}; @@ -985,4 +1071,43 @@ mod completion_tests { "#; 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; + } } From c4f683d01a4a304f5a90084b340bc2ce770b4bb9 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 10:37:34 -0300 Subject: [PATCH 03/37] Include function params for local variables completion --- tooling/lsp/src/requests/completion.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 656f436a8ab..f0a1edb7cf6 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -177,6 +177,10 @@ impl<'a> NodeFinder<'a> { noir_function: &NoirFunction, ) -> Option { self.local_variables.clear(); + for param in &noir_function.def.parameters { + self.collect_local_variables(¶m.pattern); + } + self.find_in_block_expression(&noir_function.def.body) } @@ -1110,4 +1114,22 @@ mod completion_tests { ) .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; + } } From 6163a0ef41a1db37fa07b14f903bc1f7e059ab14 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 10:38:50 -0300 Subject: [PATCH 04/37] Collect in all pattern kinds --- tooling/lsp/src/requests/completion.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index f0a1edb7cf6..010fc95e777 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -522,9 +522,17 @@ impl<'a> NodeFinder<'a> { Pattern::Identifier(ident) => { self.local_variables.insert(ident.to_string(), ident.span()); } - Pattern::Mutable(_, _, _) => todo!(), - Pattern::Tuple(_, _) => todo!(), - Pattern::Struct(_, _, _) => todo!(), + Pattern::Mutable(pattern, _, _) => self.collect_local_variables(pattern), + Pattern::Tuple(patterns, _) => { + for pattern in patterns { + self.collect_local_variables(pattern); + } + } + Pattern::Struct(_, patterns, _) => { + for (_, pattern) in patterns { + self.collect_local_variables(pattern); + } + } } } From b07e64217563e628fd471df5362ce927fa0a6410 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 16:43:38 -0300 Subject: [PATCH 05/37] Complete functions as snippets --- tooling/lsp/src/requests/completion.rs | 148 +++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 12 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 010fc95e777..388c41ca8d2 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -7,7 +7,7 @@ use async_lsp::ResponseError; use fm::{FileId, PathString}; use lsp_types::{ CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionParams, - CompletionResponse, + CompletionResponse, InsertTextFormat, }; use noirc_errors::{Location, Span}; use noirc_frontend::{ @@ -20,6 +20,7 @@ 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}, parser::{Item, ItemKind}, @@ -36,6 +37,12 @@ enum ModuleCompletionKind { AllVisibleItems, } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum FunctionCompleteKind { + Name, + NameAndParameters, +} + pub(crate) fn on_completion_request( state: &mut LspState, params: CompletionParams, @@ -372,6 +379,7 @@ impl<'a> NodeFinder<'a> { } else { ModuleCompletionKind::AllVisibleItems }; + let function_completion_kind = FunctionCompleteKind::NameAndParameters; let response = self.complete_in_module( module_id, @@ -379,6 +387,7 @@ impl<'a> NodeFinder<'a> { path.kind, at_root, module_completion_kind, + function_completion_kind, ); if is_single_segment { @@ -474,6 +483,7 @@ impl<'a> NodeFinder<'a> { } let module_completion_kind = ModuleCompletionKind::DirectChildren; + let function_completion_kind = FunctionCompleteKind::Name; if after_colons { // We are right after "::" @@ -488,6 +498,7 @@ impl<'a> NodeFinder<'a> { path_kind, at_root, module_completion_kind, + function_completion_kind, ) }) } else { @@ -501,6 +512,7 @@ impl<'a> NodeFinder<'a> { path_kind, at_root, module_completion_kind, + function_completion_kind, ) } else { let at_root = false; @@ -511,6 +523,7 @@ impl<'a> NodeFinder<'a> { path_kind, at_root, module_completion_kind, + function_completion_kind, ) }) } @@ -543,6 +556,7 @@ impl<'a> NodeFinder<'a> { path_kind: PathKind, at_root: bool, module_completion_kind: ModuleCompletionKind, + function_completion_kind: FunctionCompleteKind, ) -> Option { let def_map = &self.def_maps[&module_id.krate]; let mut module_data = def_map.modules().get(module_id.local_id.0)?; @@ -573,13 +587,19 @@ impl<'a> NodeFinder<'a> { if name_matches(name, &prefix) { let per_ns = module_data.find_name(ident); if let Some((module_def_id, _, _)) = per_ns.types { - completion_items - .push(self.module_def_id_completion_item(module_def_id, name.clone())); + completion_items.push(self.module_def_id_completion_item( + module_def_id, + name.clone(), + function_completion_kind, + )); } if let Some((module_def_id, _, _)) = per_ns.values { - completion_items - .push(self.module_def_id_completion_item(module_def_id, name.clone())); + completion_items.push(self.module_def_id_completion_item( + module_def_id, + name.clone(), + function_completion_kind, + )); } } } @@ -620,10 +640,13 @@ impl<'a> NodeFinder<'a> { &self, module_def_id: ModuleDefId, name: String, + function_completion_kind: FunctionCompleteKind, ) -> CompletionItem { match module_def_id { ModuleDefId::ModuleId(_) => module_completion_item(name), - ModuleDefId::FunctionId(func_id) => self.function_completion_item(func_id), + ModuleDefId::FunctionId(func_id) => { + self.function_completion_item(func_id, function_completion_kind) + } ModuleDefId::TypeId(struct_id) => self.struct_completion_item(struct_id), ModuleDefId::TypeAliasId(type_alias_id) => { self.type_alias_completion_item(type_alias_id) @@ -633,15 +656,67 @@ impl<'a> NodeFinder<'a> { } } - fn function_completion_item(&self, func_id: FuncId) -> CompletionItem { + fn function_completion_item( + &self, + func_id: FuncId, + function_completion_kind: FunctionCompleteKind, + ) -> CompletionItem { + let func_meta = self.interner.function_meta(&func_id); let name = self.interner.function_name(&func_id).to_string(); - let mut typ = &self.interner.function_meta(&func_id).typ; - if let Type::Forall(_, typ_) = typ { - typ = typ_; + + match function_completion_kind { + FunctionCompleteKind::Name => { + let mut typ = &func_meta.typ; + if let Type::Forall(_, typ_) = typ { + typ = typ_; + } + let description = typ.to_string(); + + simple_completion_item(name, CompletionItemKind::FUNCTION, Some(description)) + } + FunctionCompleteKind::NameAndParameters => { + let mut typ = &func_meta.typ; + if let Type::Forall(_, typ_) = typ { + typ = typ_; + } + + let label = format!("{}(…)", name); + let kind = CompletionItemKind::FUNCTION; + let description = Some(typ.to_string()); + let insert_text = self.compute_function_insert_text(&func_meta, &name); + + snippet_completion_item(label, kind, insert_text, description) + } } - let description = typ.to_string(); + } - simple_completion_item(name, CompletionItemKind::FUNCTION, Some(description)) + fn compute_function_insert_text(&self, func_meta: &FuncMeta, name: &String) -> String { + let mut text = String::new(); + text.push_str(name); + text.push('('); + for (index, (pattern, _, _)) in func_meta.parameters.0.iter().enumerate() { + if index > 0 { + text.push_str(", "); + } + + text.push_str("${"); + text.push_str(&(index + 1).to_string()); + text.push(':'); + self.hir_pattern_to_argument(pattern, &mut text); + text.push('}'); + } + 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 struct_completion_item(&self, struct_id: StructId) -> CompletionItem { @@ -741,6 +816,34 @@ fn simple_completion_item( } } +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: None, + filter_text: None, + insert_text_mode: None, + text_edit: None, + additional_text_edits: None, + command: None, + commit_characters: None, + data: None, + tags: None, + } +} + fn merge_completion_responses( response1: Option, response2: Option, @@ -1140,4 +1243,25 @@ mod completion_tests { ) .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; + } } From 711f177d81df5d6816a356f84b34e6ec6362745b Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 16:54:17 -0300 Subject: [PATCH 06/37] Suggest predefined functions too --- tooling/lsp/src/requests/completion.rs | 88 ++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 11 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 388c41ca8d2..4557b1d073a 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -383,7 +383,7 @@ impl<'a> NodeFinder<'a> { let response = self.complete_in_module( module_id, - prefix.clone(), + &prefix, path.kind, at_root, module_completion_kind, @@ -391,18 +391,23 @@ impl<'a> NodeFinder<'a> { ); if is_single_segment { - let local_vars_response = self.local_variables_completion(prefix); - merge_completion_responses(response, local_vars_response) + let local_vars_response = self.local_variables_completion(&prefix); + let response = merge_completion_responses(response, local_vars_response); + + let predefined_response = predefined_functions_completion(&prefix); + let response = merge_completion_responses(response, predefined_response); + + response } else { response } } - fn local_variables_completion(&self, prefix: String) -> Option { + fn local_variables_completion(&self, prefix: &String) -> Option { let mut completion_items = Vec::new(); for (name, span) in &self.local_variables { - if name_matches(name, &prefix) { + if name_matches(name, prefix) { let location = Location::new(*span, self.file); let description = if let Some(ReferenceId::Local(definition_id)) = self.interner.reference_at_location(location) @@ -494,7 +499,7 @@ impl<'a> NodeFinder<'a> { let at_root = false; self.complete_in_module( module_id, - prefix, + &prefix, path_kind, at_root, module_completion_kind, @@ -508,7 +513,7 @@ impl<'a> NodeFinder<'a> { let at_root = true; self.complete_in_module( self.module_id, - prefix, + &prefix, path_kind, at_root, module_completion_kind, @@ -519,7 +524,7 @@ impl<'a> NodeFinder<'a> { self.resolve_module(segments).and_then(|module_id| { self.complete_in_module( module_id, - prefix, + &prefix, path_kind, at_root, module_completion_kind, @@ -552,7 +557,7 @@ impl<'a> NodeFinder<'a> { fn complete_in_module( &self, module_id: ModuleId, - prefix: String, + prefix: &String, path_kind: PathKind, at_root: bool, module_completion_kind: ModuleCompletionKind, @@ -584,7 +589,7 @@ impl<'a> NodeFinder<'a> { for ident in items.names() { let name = &ident.0.contents; - if name_matches(name, &prefix) { + if name_matches(name, prefix) { let per_ns = module_data.find_name(ident); if let Some((module_def_id, _, _)) = per_ns.types { completion_items.push(self.module_def_id_completion_item( @@ -607,7 +612,7 @@ impl<'a> NodeFinder<'a> { if at_root && path_kind == PathKind::Plain { for dependency in self.dependencies { let dependency_name = dependency.as_name(); - if name_matches(&dependency_name, &prefix) { + if name_matches(&dependency_name, prefix) { completion_items.push(crate_completion_item(dependency_name)); } } @@ -781,6 +786,34 @@ fn name_matches(name: &str, prefix: &str) -> bool { name.starts_with(prefix) } +fn predefined_functions_completion(prefix: &String) -> Option { + let mut completion_items = Vec::new(); + + if name_matches("assert", prefix) { + completion_items.push(snippet_completion_item( + "assert(…)", + CompletionItemKind::FUNCTION, + "assert(${1:predicate})", + None, + )); + } + + if name_matches("assert_eq", prefix) { + completion_items.push(snippet_completion_item( + "assert_eq(…)", + CompletionItemKind::FUNCTION, + "assert_eq(${1:lhs}, ${2:rhs})", + None, + )); + } + + if completion_items.is_empty() { + None + } else { + Some(CompletionResponse::Array(completion_items)) + } +} + fn module_completion_item(name: impl Into) -> CompletionItem { simple_completion_item(name, CompletionItemKind::MODULE, None) } @@ -1264,4 +1297,37 @@ mod completion_tests { ) .await; } + + #[test] + async fn test_complete_predefined_functions() { + let src = r#" + fn main() { + a>|< + } + "#; + assert_completion( + src, + vec![ + snippet_completion_item( + "assert(…)", + CompletionItemKind::FUNCTION, + "assert(${1:predicate})", + None, + ), + 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})", + None, + ), + ], + ) + .await; + } } From aa14ff80476ee9623b50569f351876af6d681f7d Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 16:55:45 -0300 Subject: [PATCH 07/37] Show description for predefined functions --- tooling/lsp/src/requests/completion.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 4557b1d073a..44abf02909a 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -794,7 +794,7 @@ fn predefined_functions_completion(prefix: &String) -> Option Option Date: Sun, 11 Aug 2024 17:01:47 -0300 Subject: [PATCH 08/37] Remove " -> ()" suffix from function signatures --- tooling/lsp/src/requests/completion.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 44abf02909a..c99f5822dd0 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -669,28 +669,23 @@ impl<'a> NodeFinder<'a> { let func_meta = self.interner.function_meta(&func_id); let name = self.interner.function_name(&func_id).to_string(); + let mut typ = &func_meta.typ; + if let Type::Forall(_, typ_) = typ { + typ = typ_; + } + let description = typ.to_string(); + let description = description.strip_suffix(" -> ()").unwrap_or(&description).to_string(); + match function_completion_kind { FunctionCompleteKind::Name => { - let mut typ = &func_meta.typ; - if let Type::Forall(_, typ_) = typ { - typ = typ_; - } - let description = typ.to_string(); - simple_completion_item(name, CompletionItemKind::FUNCTION, Some(description)) } FunctionCompleteKind::NameAndParameters => { - let mut typ = &func_meta.typ; - if let Type::Forall(_, typ_) = typ { - typ = typ_; - } - let label = format!("{}(…)", name); let kind = CompletionItemKind::FUNCTION; - let description = Some(typ.to_string()); let insert_text = self.compute_function_insert_text(&func_meta, &name); - snippet_completion_item(label, kind, insert_text, description) + snippet_completion_item(label, kind, insert_text, Some(description)) } } } @@ -1292,7 +1287,7 @@ mod completion_tests { "hello(…)", CompletionItemKind::FUNCTION, "hello(${1:x}, ${2:y})", - Some("fn(i32, Field) -> ()".to_string()), + Some("fn(i32, Field)".to_string()), )], ) .await; @@ -1318,7 +1313,7 @@ mod completion_tests { "assert_constant(…)", CompletionItemKind::FUNCTION, "assert_constant(${1:x})", - Some("fn(T) -> ()".to_string()), + Some("fn(T)".to_string()), ), snippet_completion_item( "assert_eq(…)", From 3bfadaf7c8f7077eedfad71a47425cec196fe68c Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 17:10:39 -0300 Subject: [PATCH 09/37] Find inside function in impl and trait impl --- tooling/lsp/src/requests/completion.rs | 90 ++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index c99f5822dd0..03ed63232fd 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -12,8 +12,8 @@ use lsp_types::{ use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - BlockExpression, Expression, Ident, LetStatement, NoirFunction, Path, PathKind, - PathSegment, Pattern, Statement, UseTree, UseTreeKind, + BlockExpression, Expression, Ident, LetStatement, NoirFunction, NoirTraitImpl, Path, + PathKind, PathSegment, Pattern, Statement, TraitImplItem, TypeImpl, UseTree, UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ @@ -172,10 +172,13 @@ impl<'a> NodeFinder<'a> { completion } ItemKind::Function(noir_function) => self.find_in_noir_function(noir_function), - _ => { - // TODO - None - } + ItemKind::TraitImpl(noir_trait_impl) => self.find_in_noir_trait_impl(noir_trait_impl), + ItemKind::Struct(_) => todo!(), + ItemKind::Trait(_) => todo!(), + ItemKind::Impl(type_impl) => self.find_in_type_impl(type_impl), + ItemKind::TypeAlias(_) => todo!(), + ItemKind::Global(_) => todo!(), + ItemKind::ModuleDecl(_) => todo!(), } } @@ -191,6 +194,36 @@ impl<'a> NodeFinder<'a> { self.find_in_block_expression(&noir_function.def.body) } + fn find_in_noir_trait_impl( + &mut self, + noir_trait_impl: &NoirTraitImpl, + ) -> Option { + for item in &noir_trait_impl.items { + if let Some(completion) = self.find_in_trait_impl_item(item) { + return Some(completion); + } + } + None + } + + fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) -> Option { + match item { + TraitImplItem::Function(noir_function) => self.find_in_noir_function(noir_function), + TraitImplItem::Constant(_, _, _) => None, + TraitImplItem::Type { .. } => None, + } + } + + fn find_in_type_impl(&mut self, type_impl: &TypeImpl) -> Option { + for (method, _) in &type_impl.methods { + if let Some(completion) = self.find_in_noir_function(method) { + return Some(completion); + } + } + + None + } + fn find_in_block_expression( &mut self, block_expression: &BlockExpression, @@ -1325,4 +1358,49 @@ mod completion_tests { ) .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; + } } From 63ad2b85884f11ce66265dc8df325b172edbc5b9 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 17:54:28 -0300 Subject: [PATCH 10/37] Traverse the entire AST --- tooling/lsp/src/requests/completion.rs | 385 ++++++++++++++++++------- 1 file changed, 287 insertions(+), 98 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 03ed63232fd..d17fdbc4e8b 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -12,8 +12,11 @@ use lsp_types::{ use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ - BlockExpression, Expression, Ident, LetStatement, NoirFunction, NoirTraitImpl, Path, - PathKind, PathSegment, Pattern, Statement, TraitImplItem, TypeImpl, UseTree, UseTreeKind, + ArrayLiteral, AsTraitPath, BlockExpression, CallExpression, CastExpression, + ConstrainStatement, ConstructorExpression, Expression, ForLoopStatement, ForRange, Ident, + IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, + MemberAccessExpression, MethodCallExpression, NoirFunction, NoirTraitImpl, Path, PathKind, + PathSegment, Pattern, Statement, TraitImplItem, TypeImpl, UseTree, UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ @@ -173,12 +176,12 @@ impl<'a> NodeFinder<'a> { } ItemKind::Function(noir_function) => self.find_in_noir_function(noir_function), ItemKind::TraitImpl(noir_trait_impl) => self.find_in_noir_trait_impl(noir_trait_impl), - ItemKind::Struct(_) => todo!(), - ItemKind::Trait(_) => todo!(), ItemKind::Impl(type_impl) => self.find_in_type_impl(type_impl), + ItemKind::Global(let_statement) => self.find_in_let_statement(let_statement, false), ItemKind::TypeAlias(_) => todo!(), - ItemKind::Global(_) => todo!(), - ItemKind::ModuleDecl(_) => todo!(), + ItemKind::Struct(_) => todo!(), + ItemKind::Trait(_) => todo!(), + ItemKind::ModuleDecl(_) => None, } } @@ -239,143 +242,329 @@ impl<'a> NodeFinder<'a> { fn find_in_statement(&mut self, statement: &Statement) -> Option { match &statement.kind { noirc_frontend::ast::StatementKind::Let(let_statement) => { - self.find_in_let_statement(let_statement) + self.find_in_let_statement(let_statement, true) } - noirc_frontend::ast::StatementKind::Constrain(_) => { - // TODO - None + noirc_frontend::ast::StatementKind::Constrain(constrain_statement) => { + self.find_in_constrain_statement(constrain_statement) } noirc_frontend::ast::StatementKind::Expression(expression) => { self.find_in_expression(expression) } - noirc_frontend::ast::StatementKind::Assign(_) => { - // TODO - None + noirc_frontend::ast::StatementKind::Assign(assign_statement) => { + self.find_in_assign_statement(assign_statement) } - noirc_frontend::ast::StatementKind::For(_) => { - // TODO - None - } - noirc_frontend::ast::StatementKind::Break => { - // TODO - None - } - noirc_frontend::ast::StatementKind::Continue => { - // TODO - None + noirc_frontend::ast::StatementKind::For(for_loop_statement) => { + self.find_in_for_loop_statement(for_loop_statement) } - noirc_frontend::ast::StatementKind::Comptime(_) => { - // TODO - None - } - noirc_frontend::ast::StatementKind::Semi(_) => { - // TODO - None + noirc_frontend::ast::StatementKind::Comptime(statement) => { + // When entering a comptime block, regular local variables shouldn't be offered anymore + let old_local_variables = self.local_variables.clone(); + self.local_variables.clear(); + + let response = self.find_in_statement(&statement); + self.local_variables = old_local_variables; + response } - noirc_frontend::ast::StatementKind::Error => { - // TODO - None + noirc_frontend::ast::StatementKind::Semi(expression) => { + self.find_in_expression(expression) } + noirc_frontend::ast::StatementKind::Break + | noirc_frontend::ast::StatementKind::Continue + | noirc_frontend::ast::StatementKind::Error => None, } } fn find_in_let_statement( &mut self, let_statement: &LetStatement, + collect_local_variables: bool, ) -> Option { if let Some(response) = self.find_in_expression(&let_statement.expression) { return Some(response); } - self.collect_local_variables(&let_statement.pattern); + if collect_local_variables { + self.collect_local_variables(&let_statement.pattern); + } None } - fn find_in_expression(&mut self, expression: &Expression) -> Option { - match &expression.kind { - noirc_frontend::ast::ExpressionKind::Literal(_) => { - // TODO - None + fn find_in_constrain_statement( + &mut self, + constrain_statement: &ConstrainStatement, + ) -> Option { + if let Some(response) = self.find_in_expression(&constrain_statement.0) { + return Some(response); + } + + if let Some(exp) = &constrain_statement.1 { + self.find_in_expression(exp) + } else { + None + } + } + + fn find_in_assign_statement( + &mut self, + assign_statement: &noirc_frontend::ast::AssignStatement, + ) -> Option { + if let Some(response) = self.find_in_lvalue(&assign_statement.lvalue) { + return Some(response); + } + + self.find_in_expression(&assign_statement.expression) + } + + fn find_in_for_loop_statement( + &mut self, + for_loop_statement: &ForLoopStatement, + ) -> Option { + if let Some(response) = self.find_in_for_range(&for_loop_statement.range) { + return Some(response); + } + + self.find_in_expression(&for_loop_statement.block) + } + + fn find_in_lvalue(&mut self, lvalue: &LValue) -> Option { + match lvalue { + LValue::Ident(_) => None, + LValue::MemberAccess { object, field_name: _, span: _ } => self.find_in_lvalue(object), + LValue::Index { array, index, span: _ } => { + if let Some(response) = self.find_in_lvalue(array) { + return Some(response); + } + + self.find_in_expression(index) } - noirc_frontend::ast::ExpressionKind::Block(_) => { - // TODO - None + LValue::Dereference(lvalue, _) => self.find_in_lvalue(lvalue), + } + } + + fn find_in_for_range(&mut self, for_range: &ForRange) -> Option { + match for_range { + ForRange::Range(start, end) => { + if let Some(response) = self.find_in_expression(start) { + return Some(response); + } + + self.find_in_expression(end) } - noirc_frontend::ast::ExpressionKind::Prefix(_) => { - // TODO - None + ForRange::Array(expression) => self.find_in_expression(expression), + } + } + + fn find_in_expressions(&mut self, expressions: &[Expression]) -> Option { + for expression in expressions { + if let Some(response) = self.find_in_expression(expression) { + return Some(response); } - noirc_frontend::ast::ExpressionKind::Index(_) => { - // TODO - None + } + None + } + + fn find_in_expression(&mut self, expression: &Expression) -> Option { + match &expression.kind { + noirc_frontend::ast::ExpressionKind::Literal(literal) => self.find_in_literal(literal), + noirc_frontend::ast::ExpressionKind::Block(block_expression) => { + self.find_in_block_expression(block_expression) } - noirc_frontend::ast::ExpressionKind::Call(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Prefix(prefix_expression) => { + self.find_in_expression(&prefix_expression.rhs) } - noirc_frontend::ast::ExpressionKind::MethodCall(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Index(index_expression) => { + self.find_in_index_expression(&index_expression) } - noirc_frontend::ast::ExpressionKind::Constructor(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Call(call_expression) => { + self.find_in_call_expression(call_expression) } - noirc_frontend::ast::ExpressionKind::MemberAccess(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::MethodCall(method_call_expression) => { + self.find_in_method_call_expression(method_call_expression) } - noirc_frontend::ast::ExpressionKind::Cast(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Constructor(constructor_expression) => { + self.find_in_constructor_expression(constructor_expression) } - noirc_frontend::ast::ExpressionKind::Infix(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::MemberAccess(member_access_expression) => { + self.find_in_member_access_expression(member_access_expression) } - noirc_frontend::ast::ExpressionKind::If(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Cast(cast_expression) => { + self.find_in_cast_expression(cast_expression) } - noirc_frontend::ast::ExpressionKind::Variable(path) => self.find_in_path(path), - noirc_frontend::ast::ExpressionKind::Tuple(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Infix(infix_expression) => { + self.find_in_infix_expression(infix_expression) } - noirc_frontend::ast::ExpressionKind::Lambda(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::If(if_expression) => { + self.find_in_if_expression(if_expression) } - noirc_frontend::ast::ExpressionKind::Parenthesized(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Variable(path) => self.find_in_path(path), + noirc_frontend::ast::ExpressionKind::Tuple(expressions) => { + self.find_in_expressions(expressions) } - noirc_frontend::ast::ExpressionKind::Quote(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Lambda(lambda) => self.find_in_lambda(lambda), + noirc_frontend::ast::ExpressionKind::Parenthesized(expression) => { + self.find_in_expression(expression) } - noirc_frontend::ast::ExpressionKind::Unquote(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Unquote(expression) => { + self.find_in_expression(expression) } - noirc_frontend::ast::ExpressionKind::Comptime(_, _) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Comptime(block_expression, _) => { + // When entering a comptime block, regular local variables shouldn't be offered anymore + let old_local_variables = self.local_variables.clone(); + self.local_variables.clear(); + + let response = self.find_in_block_expression(&block_expression); + self.local_variables = old_local_variables; + response } - noirc_frontend::ast::ExpressionKind::AsTraitPath(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::AsTraitPath(as_trait_path) => { + self.find_in_as_trait_path(as_trait_path) } - noirc_frontend::ast::ExpressionKind::Resolved(_) => { - // TODO - None + noirc_frontend::ast::ExpressionKind::Quote(_) + | noirc_frontend::ast::ExpressionKind::Resolved(_) + | noirc_frontend::ast::ExpressionKind::Error => None, + } + } + + fn find_in_literal(&mut self, literal: &Literal) -> Option { + match literal { + Literal::Array(array_literal) => self.find_in_array_literal(array_literal), + Literal::Slice(array_literal) => self.find_in_array_literal(array_literal), + Literal::Bool(_) + | Literal::Integer(_, _) + | Literal::Str(_) + | Literal::RawStr(_, _) + | Literal::FmtStr(_) + | Literal::Unit => None, + } + } + + fn find_in_array_literal( + &mut self, + array_literal: &ArrayLiteral, + ) -> Option { + match array_literal { + ArrayLiteral::Standard(expressions) => self.find_in_expressions(expressions), + ArrayLiteral::Repeated { repeated_element, length } => { + if let Some(completion) = self.find_in_expression(repeated_element) { + return Some(completion); + } + + self.find_in_expression(length) } - noirc_frontend::ast::ExpressionKind::Error => { - // TODO - None + } + } + + fn find_in_index_expression( + &mut self, + index_expression: &IndexExpression, + ) -> Option { + if let Some(response) = self.find_in_expression(&index_expression.collection) { + return Some(response); + } + + self.find_in_expression(&index_expression.index) + } + + fn find_in_call_expression( + &mut self, + call_expression: &CallExpression, + ) -> Option { + if let Some(response) = self.find_in_expression(&call_expression.func) { + return Some(response); + } + + self.find_in_expressions(&call_expression.arguments) + } + + fn find_in_method_call_expression( + &mut self, + method_call_expression: &MethodCallExpression, + ) -> Option { + if let Some(response) = self.find_in_expression(&method_call_expression.object) { + return Some(response); + } + + self.find_in_expressions(&method_call_expression.arguments) + } + + fn find_in_constructor_expression( + &mut self, + constructor_expression: &ConstructorExpression, + ) -> Option { + if let Some(response) = self.find_in_path(&constructor_expression.type_name) { + return Some(response); + } + + for (_field_name, expression) in &constructor_expression.fields { + if let Some(response) = self.find_in_expression(expression) { + return Some(response); } } + + None + } + + fn find_in_member_access_expression( + &mut self, + member_access_expression: &MemberAccessExpression, + ) -> Option { + self.find_in_expression(&member_access_expression.lhs) + } + + fn find_in_cast_expression( + &mut self, + cast_expression: &CastExpression, + ) -> Option { + self.find_in_expression(&cast_expression.lhs) + } + + fn find_in_infix_expression( + &mut self, + infix_expression: &InfixExpression, + ) -> Option { + if let Some(response) = self.find_in_expression(&infix_expression.lhs) { + return Some(response); + } + + self.find_in_expression(&infix_expression.rhs) + } + + fn find_in_if_expression( + &mut self, + if_expression: &IfExpression, + ) -> Option { + if let Some(response) = self.find_in_expression(&if_expression.condition) { + return Some(response); + } + + if let Some(response) = self.find_in_expression(&if_expression.consequence) { + return Some(response); + } + + if let Some(alternative) = &if_expression.alternative { + self.find_in_expression(alternative) + } else { + None + } + } + + fn find_in_lambda(&mut self, lambda: &Lambda) -> Option { + let old_local_variables = self.local_variables.clone(); + + for (pattern, _) in &lambda.parameters { + self.collect_local_variables(pattern) + } + + let response = self.find_in_expression(&lambda.body); + + self.local_variables = old_local_variables; + + response + } + + fn find_in_as_trait_path(&mut self, as_trait_path: &AsTraitPath) -> Option { + self.find_in_path(&as_trait_path.trait_path) } fn find_in_path(&mut self, path: &Path) -> Option { From c924fb37861fb5188ab0e3ec962936a15b594ada Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 18:01:01 -0300 Subject: [PATCH 11/37] Make sure lambda arguments and for index are suggested --- tooling/lsp/src/requests/completion.rs | 50 +++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index d17fdbc4e8b..629df38fe7f 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -320,11 +320,19 @@ impl<'a> NodeFinder<'a> { &mut self, for_loop_statement: &ForLoopStatement, ) -> Option { + let old_local_variables = self.local_variables.clone(); + let ident = &for_loop_statement.identifier; + self.local_variables.insert(ident.to_string(), ident.span()); + if let Some(response) = self.find_in_for_range(&for_loop_statement.range) { return Some(response); } - self.find_in_expression(&for_loop_statement.block) + let response = self.find_in_expression(&for_loop_statement.block); + + self.local_variables = old_local_variables; + + response } fn find_in_lvalue(&mut self, lvalue: &LValue) -> Option { @@ -1592,4 +1600,44 @@ mod completion_tests { ) .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; + } } From c066e9ae3a6fe67e36bfe37f40ddfe6ef9e4452b Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 18:09:09 -0300 Subject: [PATCH 12/37] Clean up some todo!() --- tooling/lsp/src/requests/completion.rs | 62 +++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 629df38fe7f..0470acd0497 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -15,8 +15,9 @@ use noirc_frontend::{ ArrayLiteral, AsTraitPath, BlockExpression, CallExpression, CastExpression, ConstrainStatement, ConstructorExpression, Expression, ForLoopStatement, ForRange, Ident, IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, - MemberAccessExpression, MethodCallExpression, NoirFunction, NoirTraitImpl, Path, PathKind, - PathSegment, Pattern, Statement, TraitImplItem, TypeImpl, UseTree, UseTreeKind, + MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, NoirTrait, + NoirTraitImpl, NoirTypeAlias, Path, PathKind, PathSegment, Pattern, Statement, + TraitImplItem, TraitItem, TypeImpl, UseTree, UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ @@ -178,9 +179,9 @@ impl<'a> NodeFinder<'a> { ItemKind::TraitImpl(noir_trait_impl) => self.find_in_noir_trait_impl(noir_trait_impl), ItemKind::Impl(type_impl) => self.find_in_type_impl(type_impl), ItemKind::Global(let_statement) => self.find_in_let_statement(let_statement, false), - ItemKind::TypeAlias(_) => todo!(), - ItemKind::Struct(_) => todo!(), - ItemKind::Trait(_) => todo!(), + ItemKind::TypeAlias(noir_type_alias) => self.find_in_noir_type_alias(noir_type_alias), + ItemKind::Struct(noir_struct) => self.find_in_noir_struct(noir_struct), + ItemKind::Trait(noir_trait) => self.find_in_noir_trait(noir_trait), ItemKind::ModuleDecl(_) => None, } } @@ -227,6 +228,57 @@ impl<'a> NodeFinder<'a> { None } + fn find_in_noir_type_alias( + &mut self, + _noir_type_alias: &NoirTypeAlias, + ) -> Option { + None + } + + fn find_in_noir_struct(&mut self, _noir_struct: &NoirStruct) -> Option { + None + } + + fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) -> Option { + for item in &noir_trait.items { + if let Some(response) = self.find_in_trait_item(item) { + return Some(response); + } + } + None + } + + fn find_in_trait_item(&mut self, trait_item: &TraitItem) -> Option { + match trait_item { + TraitItem::Function { + name: _, + generics: _, + parameters, + return_type: _, + where_clause: _, + body, + } => { + if let Some(body) = body { + self.local_variables.clear(); + for (name, _) in parameters { + self.local_variables.insert(name.to_string(), name.span()); + } + self.find_in_block_expression(body) + } else { + None + } + } + TraitItem::Constant { name: _, typ: _, default_value } => { + if let Some(default_value) = default_value { + self.find_in_expression(default_value) + } else { + None + } + } + TraitItem::Type { name: _ } => None, + } + } + fn find_in_block_expression( &mut self, block_expression: &BlockExpression, From 05e5e65e4ffe50e95f06971c67ca4992e22b61eb Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 18:30:16 -0300 Subject: [PATCH 13/37] Only suggest types at type position --- tooling/lsp/src/requests/completion.rs | 189 ++++++++++++++++++++++--- 1 file changed, 171 insertions(+), 18 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 0470acd0497..829979e9f5e 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -17,7 +17,7 @@ use noirc_frontend::{ IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, PathKind, PathSegment, Pattern, Statement, - TraitImplItem, TraitItem, TypeImpl, UseTree, UseTreeKind, + TraitImplItem, TraitItem, TypeImpl, UnresolvedType, UseTree, UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ @@ -47,6 +47,12 @@ enum FunctionCompleteKind { NameAndParameters, } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum PathCompletionKind { + Everything, + OnlyTypes, +} + pub(crate) fn on_completion_request( state: &mut LspState, params: CompletionParams, @@ -235,7 +241,13 @@ impl<'a> NodeFinder<'a> { None } - fn find_in_noir_struct(&mut self, _noir_struct: &NoirStruct) -> Option { + fn find_in_noir_struct(&mut self, noir_struct: &NoirStruct) -> Option { + for (_name, unresolved_type) in &noir_struct.fields { + if let Some(response) = self.find_in_unresolved_type(unresolved_type) { + return Some(response); + } + } + None } @@ -457,7 +469,9 @@ impl<'a> NodeFinder<'a> { noirc_frontend::ast::ExpressionKind::If(if_expression) => { self.find_in_if_expression(if_expression) } - noirc_frontend::ast::ExpressionKind::Variable(path) => self.find_in_path(path), + noirc_frontend::ast::ExpressionKind::Variable(path) => { + self.find_in_path(path, PathCompletionKind::Everything) + } noirc_frontend::ast::ExpressionKind::Tuple(expressions) => { self.find_in_expressions(expressions) } @@ -552,7 +566,9 @@ impl<'a> NodeFinder<'a> { &mut self, constructor_expression: &ConstructorExpression, ) -> Option { - if let Some(response) = self.find_in_path(&constructor_expression.type_name) { + if let Some(response) = + self.find_in_path(&constructor_expression.type_name, PathCompletionKind::OnlyTypes) + { return Some(response); } @@ -624,10 +640,95 @@ impl<'a> NodeFinder<'a> { } fn find_in_as_trait_path(&mut self, as_trait_path: &AsTraitPath) -> Option { - self.find_in_path(&as_trait_path.trait_path) + self.find_in_path(&as_trait_path.trait_path, PathCompletionKind::OnlyTypes) + } + + fn find_in_unresolved_types( + &mut self, + unresolved_type: &[UnresolvedType], + ) -> Option { + for unresolved_type in unresolved_type { + if let Some(response) = self.find_in_unresolved_type(unresolved_type) { + return Some(response); + } + } + + None + } + + fn find_in_unresolved_type( + &mut self, + unresolved_type: &UnresolvedType, + ) -> Option { + if let Some(span) = unresolved_type.span { + if !self.includes_span(span) { + return None; + } + } + + match &unresolved_type.typ { + noirc_frontend::ast::UnresolvedTypeData::Array(_, unresolved_type) => { + self.find_in_unresolved_type(unresolved_type) + } + noirc_frontend::ast::UnresolvedTypeData::Slice(unresolved_type) => { + self.find_in_unresolved_type(unresolved_type) + } + noirc_frontend::ast::UnresolvedTypeData::Parenthesized(unresolved_type) => { + self.find_in_unresolved_type(unresolved_type) + } + noirc_frontend::ast::UnresolvedTypeData::Named(path, unresolved_types, _) => { + if let Some(response) = self.find_in_path(path, PathCompletionKind::OnlyTypes) { + return Some(response); + } + + self.find_in_unresolved_types(unresolved_types) + } + noirc_frontend::ast::UnresolvedTypeData::TraitAsType(path, unresolved_types) => { + if let Some(response) = self.find_in_path(path, PathCompletionKind::OnlyTypes) { + return Some(response); + } + + self.find_in_unresolved_types(unresolved_types) + } + noirc_frontend::ast::UnresolvedTypeData::MutableReference(unresolved_type) => { + self.find_in_unresolved_type(unresolved_type) + } + noirc_frontend::ast::UnresolvedTypeData::Tuple(unresolved_types) => { + self.find_in_unresolved_types(unresolved_types) + } + noirc_frontend::ast::UnresolvedTypeData::Function(args, ret, env) => { + if let Some(response) = self.find_in_unresolved_types(args) { + return Some(response); + } + + if let Some(response) = self.find_in_unresolved_type(ret) { + return Some(response); + } + + self.find_in_unresolved_type(env) + } + noirc_frontend::ast::UnresolvedTypeData::AsTraitPath(as_trait_path) => { + self.find_in_as_trait_path(as_trait_path) + } + noirc_frontend::ast::UnresolvedTypeData::Expression(_) + | noirc_frontend::ast::UnresolvedTypeData::FormatString(_, _) + | noirc_frontend::ast::UnresolvedTypeData::String(_) + | noirc_frontend::ast::UnresolvedTypeData::Unspecified + | noirc_frontend::ast::UnresolvedTypeData::Quoted(_) + | noirc_frontend::ast::UnresolvedTypeData::FieldElement + | noirc_frontend::ast::UnresolvedTypeData::Integer(_, _) + | noirc_frontend::ast::UnresolvedTypeData::Bool + | noirc_frontend::ast::UnresolvedTypeData::Unit + | noirc_frontend::ast::UnresolvedTypeData::Resolved(_) + | noirc_frontend::ast::UnresolvedTypeData::Error => None, + } } - fn find_in_path(&mut self, path: &Path) -> Option { + fn find_in_path( + &mut self, + path: &Path, + path_completion_kind: PathCompletionKind, + ) -> Option { // Only offer completions if we are right at the end of the path if self.byte_index != path.span.end() as usize { return None; @@ -670,16 +771,22 @@ impl<'a> NodeFinder<'a> { at_root, module_completion_kind, function_completion_kind, + path_completion_kind, ); if is_single_segment { - let local_vars_response = self.local_variables_completion(&prefix); - let response = merge_completion_responses(response, local_vars_response); + match path_completion_kind { + PathCompletionKind::Everything => { + let local_vars_response = self.local_variables_completion(&prefix); + let response = merge_completion_responses(response, local_vars_response); - let predefined_response = predefined_functions_completion(&prefix); - let response = merge_completion_responses(response, predefined_response); + let predefined_response = predefined_functions_completion(&prefix); + let response = merge_completion_responses(response, predefined_response); - response + response + } + PathCompletionKind::OnlyTypes => response, + } } else { response } @@ -771,6 +878,7 @@ impl<'a> NodeFinder<'a> { let module_completion_kind = ModuleCompletionKind::DirectChildren; let function_completion_kind = FunctionCompleteKind::Name; + let path_completion_kind = PathCompletionKind::Everything; if after_colons { // We are right after "::" @@ -786,6 +894,7 @@ impl<'a> NodeFinder<'a> { at_root, module_completion_kind, function_completion_kind, + path_completion_kind, ) }) } else { @@ -800,6 +909,7 @@ impl<'a> NodeFinder<'a> { at_root, module_completion_kind, function_completion_kind, + path_completion_kind, ) } else { let at_root = false; @@ -811,6 +921,7 @@ impl<'a> NodeFinder<'a> { at_root, module_completion_kind, function_completion_kind, + path_completion_kind, ) }) } @@ -844,6 +955,7 @@ impl<'a> NodeFinder<'a> { at_root: bool, module_completion_kind: ModuleCompletionKind, function_completion_kind: FunctionCompleteKind, + path_completion_kind: PathCompletionKind, ) -> Option { let def_map = &self.def_maps[&module_id.krate]; let mut module_data = def_map.modules().get(module_id.local_id.0)?; @@ -874,19 +986,25 @@ impl<'a> NodeFinder<'a> { if name_matches(name, prefix) { let per_ns = module_data.find_name(ident); if let Some((module_def_id, _, _)) = per_ns.types { - completion_items.push(self.module_def_id_completion_item( + if let Some(completion_item) = self.module_def_id_completion_item( module_def_id, name.clone(), function_completion_kind, - )); + path_completion_kind, + ) { + completion_items.push(completion_item); + } } if let Some((module_def_id, _, _)) = per_ns.values { - completion_items.push(self.module_def_id_completion_item( + if let Some(completion_item) = self.module_def_id_completion_item( module_def_id, name.clone(), function_completion_kind, - )); + path_completion_kind, + ) { + completion_items.push(completion_item); + } } } } @@ -928,8 +1046,20 @@ impl<'a> NodeFinder<'a> { module_def_id: ModuleDefId, name: String, function_completion_kind: FunctionCompleteKind, - ) -> CompletionItem { - match module_def_id { + path_completion_kind: PathCompletionKind, + ) -> Option { + match path_completion_kind { + PathCompletionKind::OnlyTypes => match module_def_id { + ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return None, + ModuleDefId::ModuleId(_) + | ModuleDefId::TypeId(_) + | ModuleDefId::TypeAliasId(_) + | ModuleDefId::TraitId(_) => (), + }, + PathCompletionKind::Everything => (), + } + + let completion_item = match module_def_id { ModuleDefId::ModuleId(_) => module_completion_item(name), ModuleDefId::FunctionId(func_id) => { self.function_completion_item(func_id, function_completion_kind) @@ -940,7 +1070,8 @@ impl<'a> NodeFinder<'a> { } ModuleDefId::TraitId(trait_id) => self.trait_completion_item(trait_id), ModuleDefId::GlobalId(global_id) => self.global_completion_item(global_id), - } + }; + Some(completion_item) } fn function_completion_item( @@ -1692,4 +1823,26 @@ mod completion_tests { ) .await; } + + #[test] + async fn test_suggest_type_in_struct_field_type() { + let src = r#" + struct Something {} + + fn SomeFunction() {} + + struct Another { + some: S>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } } From 44ab74948a015e4fa132d8fe47583643f523b16e Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 18:42:13 -0300 Subject: [PATCH 14/37] Complete types in more places --- tooling/lsp/src/requests/completion.rs | 158 +++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 10 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 829979e9f5e..d74d281378a 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -13,11 +13,11 @@ use noirc_errors::{Location, Span}; use noirc_frontend::{ ast::{ ArrayLiteral, AsTraitPath, BlockExpression, CallExpression, CastExpression, - ConstrainStatement, ConstructorExpression, Expression, ForLoopStatement, ForRange, Ident, - IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, - MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, NoirTrait, - NoirTraitImpl, NoirTypeAlias, Path, PathKind, PathSegment, Pattern, Statement, - TraitImplItem, TraitItem, TypeImpl, UnresolvedType, UseTree, UseTreeKind, + ConstrainStatement, ConstructorExpression, Expression, ForLoopStatement, ForRange, + FunctionReturnType, Ident, IfExpression, IndexExpression, InfixExpression, LValue, Lambda, + LetStatement, Literal, MemberAccessExpression, MethodCallExpression, NoirFunction, + NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, PathKind, PathSegment, Pattern, + Statement, TraitImplItem, TraitItem, TypeImpl, UnresolvedType, UseTree, UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ @@ -196,6 +196,16 @@ impl<'a> NodeFinder<'a> { &mut self, noir_function: &NoirFunction, ) -> Option { + for param in &noir_function.def.parameters { + if let Some(response) = self.find_in_unresolved_type(¶m.typ) { + return Some(response); + } + } + + if let Some(response) = self.find_in_function_return_type(&noir_function.def.return_type) { + return Some(response); + } + self.local_variables.clear(); for param in &noir_function.def.parameters { self.collect_local_variables(¶m.pattern); @@ -236,9 +246,9 @@ impl<'a> NodeFinder<'a> { fn find_in_noir_type_alias( &mut self, - _noir_type_alias: &NoirTypeAlias, + noir_type_alias: &NoirTypeAlias, ) -> Option { - None + self.find_in_unresolved_type(&noir_type_alias.typ) } fn find_in_noir_struct(&mut self, noir_struct: &NoirStruct) -> Option { @@ -266,10 +276,28 @@ impl<'a> NodeFinder<'a> { name: _, generics: _, parameters, - return_type: _, - where_clause: _, + return_type, + where_clause, body, } => { + for (_name, unresolved_type) in parameters { + if let Some(response) = self.find_in_unresolved_type(unresolved_type) { + return Some(response); + } + } + + if let Some(response) = self.find_in_function_return_type(return_type) { + return Some(response); + } + + for unresolved_trait_constraint in where_clause { + if let Some(response) = + self.find_in_unresolved_type(&unresolved_trait_constraint.typ) + { + return Some(response); + } + } + if let Some(body) = body { self.local_variables.clear(); for (name, _) in parameters { @@ -280,7 +308,11 @@ impl<'a> NodeFinder<'a> { None } } - TraitItem::Constant { name: _, typ: _, default_value } => { + TraitItem::Constant { name: _, typ, default_value } => { + if let Some(response) = self.find_in_unresolved_type(typ) { + return Some(response); + } + if let Some(default_value) = default_value { self.find_in_expression(default_value) } else { @@ -643,6 +675,18 @@ impl<'a> NodeFinder<'a> { self.find_in_path(&as_trait_path.trait_path, PathCompletionKind::OnlyTypes) } + fn find_in_function_return_type( + &mut self, + return_type: &FunctionReturnType, + ) -> Option { + match return_type { + noirc_frontend::ast::FunctionReturnType::Default(_) => None, + noirc_frontend::ast::FunctionReturnType::Ty(unresolved_type) => { + self.find_in_unresolved_type(unresolved_type) + } + } + } + fn find_in_unresolved_types( &mut self, unresolved_type: &[UnresolvedType], @@ -1845,4 +1889,98 @@ mod completion_tests { ) .await; } + + #[test] + async fn test_suggest_type_in_function_parameter() { + let src = r#" + struct Something {} + + fn foo(x: S>|<) {} + "#; + 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() -> S>|< {} + "#; + 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 = S>|< + "#; + 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: S>|<); + } + "#; + 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() -> S>|<; + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } } From 4d8c1114ba3c35281dfe8d5da8ffca8a0eec7155 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 18:46:16 -0300 Subject: [PATCH 15/37] A couple more places --- tooling/lsp/src/requests/completion.rs | 51 +++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index d74d281378a..7846bea16a5 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -375,6 +375,10 @@ impl<'a> NodeFinder<'a> { let_statement: &LetStatement, collect_local_variables: bool, ) -> Option { + if let Some(response) = self.find_in_unresolved_type(&let_statement.r#type) { + return Some(response); + } + if let Some(response) = self.find_in_expression(&let_statement.expression) { return Some(response); } @@ -658,8 +662,13 @@ impl<'a> NodeFinder<'a> { } fn find_in_lambda(&mut self, lambda: &Lambda) -> Option { - let old_local_variables = self.local_variables.clone(); + for (_, unresolved_type) in &lambda.parameters { + if let Some(response) = self.find_in_unresolved_type(unresolved_type) { + return Some(response); + } + } + let old_local_variables = self.local_variables.clone(); for (pattern, _) in &lambda.parameters { self.collect_local_variables(pattern) } @@ -1983,4 +1992,44 @@ mod completion_tests { ) .await; } + + #[test] + async fn test_suggest_type_in_let_type() { + let src = r#" + struct Something {} + + fn main() { + let x: S>|< + } + "#; + 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: S>|<| s) + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } } From ce001711c34072483bfd236dcfd32b64c3dcffc5 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 18:55:39 -0300 Subject: [PATCH 16/37] Suggest built-in types too --- tooling/lsp/src/requests/completion.rs | 63 +++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 7846bea16a5..1ded67bd668 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -838,7 +838,12 @@ impl<'a> NodeFinder<'a> { response } - PathCompletionKind::OnlyTypes => response, + PathCompletionKind::OnlyTypes => { + let builtin_types_response = builtin_types_completion(&prefix); + let response = merge_completion_responses(response, builtin_types_response); + + response + } } } else { response @@ -1275,6 +1280,45 @@ fn predefined_functions_completion(prefix: &String) -> Option Option { + let mut completion_items = Vec::new(); + + for typ in [ + "bool", + "i8", + "i16", + "i32", + "i64", + "u8", + "u16", + "u32", + "u64", + "str", + "Expr", + "Field", + "FunctionDefinition", + "Quoted", + "StructDefinition", + "TraitConstraint", + "TraitDefinition", + "Type", + ] { + if name_matches(typ, prefix) { + completion_items.push(simple_completion_item( + typ, + CompletionItemKind::STRUCT, + Some(typ.to_string()), + )); + } + } + + if completion_items.is_empty() { + None + } else { + Some(CompletionResponse::Array(completion_items)) + } +} + fn module_completion_item(name: impl Into) -> CompletionItem { simple_completion_item(name, CompletionItemKind::MODULE, None) } @@ -2032,4 +2076,21 @@ mod completion_tests { ) .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; + } } From f41b0ff6fef764cd06ed7f68a81cd4d79a5736ea Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 18:57:42 -0300 Subject: [PATCH 17/37] Better names --- tooling/lsp/src/requests/completion.rs | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 1ded67bd668..1945bca9ddb 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -48,8 +48,8 @@ enum FunctionCompleteKind { } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum PathCompletionKind { - Everything, +enum RequestedItems { + AnyItems, OnlyTypes, } @@ -506,7 +506,7 @@ impl<'a> NodeFinder<'a> { self.find_in_if_expression(if_expression) } noirc_frontend::ast::ExpressionKind::Variable(path) => { - self.find_in_path(path, PathCompletionKind::Everything) + self.find_in_path(path, RequestedItems::AnyItems) } noirc_frontend::ast::ExpressionKind::Tuple(expressions) => { self.find_in_expressions(expressions) @@ -603,7 +603,7 @@ impl<'a> NodeFinder<'a> { constructor_expression: &ConstructorExpression, ) -> Option { if let Some(response) = - self.find_in_path(&constructor_expression.type_name, PathCompletionKind::OnlyTypes) + self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes) { return Some(response); } @@ -681,7 +681,7 @@ impl<'a> NodeFinder<'a> { } fn find_in_as_trait_path(&mut self, as_trait_path: &AsTraitPath) -> Option { - self.find_in_path(&as_trait_path.trait_path, PathCompletionKind::OnlyTypes) + self.find_in_path(&as_trait_path.trait_path, RequestedItems::OnlyTypes) } fn find_in_function_return_type( @@ -730,14 +730,14 @@ impl<'a> NodeFinder<'a> { self.find_in_unresolved_type(unresolved_type) } noirc_frontend::ast::UnresolvedTypeData::Named(path, unresolved_types, _) => { - if let Some(response) = self.find_in_path(path, PathCompletionKind::OnlyTypes) { + if let Some(response) = self.find_in_path(path, RequestedItems::OnlyTypes) { return Some(response); } self.find_in_unresolved_types(unresolved_types) } noirc_frontend::ast::UnresolvedTypeData::TraitAsType(path, unresolved_types) => { - if let Some(response) = self.find_in_path(path, PathCompletionKind::OnlyTypes) { + if let Some(response) = self.find_in_path(path, RequestedItems::OnlyTypes) { return Some(response); } @@ -780,7 +780,7 @@ impl<'a> NodeFinder<'a> { fn find_in_path( &mut self, path: &Path, - path_completion_kind: PathCompletionKind, + requested_items: RequestedItems, ) -> Option { // Only offer completions if we are right at the end of the path if self.byte_index != path.span.end() as usize { @@ -824,12 +824,12 @@ impl<'a> NodeFinder<'a> { at_root, module_completion_kind, function_completion_kind, - path_completion_kind, + requested_items, ); if is_single_segment { - match path_completion_kind { - PathCompletionKind::Everything => { + match requested_items { + RequestedItems::AnyItems => { let local_vars_response = self.local_variables_completion(&prefix); let response = merge_completion_responses(response, local_vars_response); @@ -838,7 +838,7 @@ impl<'a> NodeFinder<'a> { response } - PathCompletionKind::OnlyTypes => { + RequestedItems::OnlyTypes => { let builtin_types_response = builtin_types_completion(&prefix); let response = merge_completion_responses(response, builtin_types_response); @@ -936,7 +936,7 @@ impl<'a> NodeFinder<'a> { let module_completion_kind = ModuleCompletionKind::DirectChildren; let function_completion_kind = FunctionCompleteKind::Name; - let path_completion_kind = PathCompletionKind::Everything; + let requested_items = RequestedItems::AnyItems; if after_colons { // We are right after "::" @@ -952,7 +952,7 @@ impl<'a> NodeFinder<'a> { at_root, module_completion_kind, function_completion_kind, - path_completion_kind, + requested_items, ) }) } else { @@ -967,7 +967,7 @@ impl<'a> NodeFinder<'a> { at_root, module_completion_kind, function_completion_kind, - path_completion_kind, + requested_items, ) } else { let at_root = false; @@ -979,7 +979,7 @@ impl<'a> NodeFinder<'a> { at_root, module_completion_kind, function_completion_kind, - path_completion_kind, + requested_items, ) }) } @@ -1013,7 +1013,7 @@ impl<'a> NodeFinder<'a> { at_root: bool, module_completion_kind: ModuleCompletionKind, function_completion_kind: FunctionCompleteKind, - path_completion_kind: PathCompletionKind, + requested_items: RequestedItems, ) -> Option { let def_map = &self.def_maps[&module_id.krate]; let mut module_data = def_map.modules().get(module_id.local_id.0)?; @@ -1048,7 +1048,7 @@ impl<'a> NodeFinder<'a> { module_def_id, name.clone(), function_completion_kind, - path_completion_kind, + requested_items, ) { completion_items.push(completion_item); } @@ -1059,7 +1059,7 @@ impl<'a> NodeFinder<'a> { module_def_id, name.clone(), function_completion_kind, - path_completion_kind, + requested_items, ) { completion_items.push(completion_item); } @@ -1104,17 +1104,17 @@ impl<'a> NodeFinder<'a> { module_def_id: ModuleDefId, name: String, function_completion_kind: FunctionCompleteKind, - path_completion_kind: PathCompletionKind, + requested_items: RequestedItems, ) -> Option { - match path_completion_kind { - PathCompletionKind::OnlyTypes => match module_def_id { + match requested_items { + RequestedItems::OnlyTypes => match module_def_id { ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return None, ModuleDefId::ModuleId(_) | ModuleDefId::TypeId(_) | ModuleDefId::TypeAliasId(_) | ModuleDefId::TraitId(_) => (), }, - PathCompletionKind::Everything => (), + RequestedItems::AnyItems => (), } let completion_item = match module_def_id { From 0703cadbc2773dd40d8181f6d730493545c1fbd3 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Sun, 11 Aug 2024 19:01:03 -0300 Subject: [PATCH 18/37] Also suggest true and false --- tooling/lsp/src/requests/completion.rs | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 1945bca9ddb..c28ecf03677 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -1273,6 +1273,16 @@ fn predefined_functions_completion(prefix: &String) -> Option|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "true", + CompletionItemKind::KEYWORD, + Some("bool".to_string()), + )], + ) + .await; + } } From 284a11c5cecab74772cc87729fe58b0cc3b2b3a9 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 05:19:47 -0300 Subject: [PATCH 19/37] Correctly handle if scope --- tooling/lsp/src/requests/completion.rs | 92 +++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index c28ecf03677..505c1b6a581 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -650,15 +650,21 @@ impl<'a> NodeFinder<'a> { return Some(response); } - if let Some(response) = self.find_in_expression(&if_expression.consequence) { + let old_local_variables = self.local_variables.clone(); + let response = self.find_in_expression(&if_expression.consequence); + self.local_variables = old_local_variables; + if let Some(response) = response { return Some(response); } - if let Some(alternative) = &if_expression.alternative { + let old_local_variables = self.local_variables.clone(); + let response = if let Some(alternative) = &if_expression.alternative { self.find_in_expression(alternative) } else { None - } + }; + self.local_variables = old_local_variables; + response } fn find_in_lambda(&mut self, lambda: &Lambda) -> Option { @@ -2121,4 +2127,84 @@ mod completion_tests { ) .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; + } } From 0c23aa5e8519e34c1b1753c21f704fc5fa694267 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 05:21:40 -0300 Subject: [PATCH 20/37] Correctly handle block scope --- tooling/lsp/src/requests/completion.rs | 59 ++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 505c1b6a581..2533a6dacb5 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -327,12 +327,17 @@ impl<'a> NodeFinder<'a> { &mut self, block_expression: &BlockExpression, ) -> Option { + let old_local_variables = self.local_variables.clone(); + let mut completion = None; for statement in &block_expression.statements { - if let Some(completion) = self.find_in_statement(statement) { - return Some(completion); + completion = self.find_in_statement(statement); + if completion.is_some() { + break; } } - None + self.local_variables = old_local_variables; + + completion } fn find_in_statement(&mut self, statement: &Statement) -> Option { @@ -2207,4 +2212,52 @@ mod completion_tests { ) .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; + } } From 50122489bd1d551997f1d830277648918b9e52ab Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 05:42:37 -0300 Subject: [PATCH 21/37] Suggest type parameters in more places --- tooling/lsp/src/requests/completion.rs | 175 ++++++++++++++++++++++--- 1 file changed, 159 insertions(+), 16 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 2533a6dacb5..0a27ff69c44 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, HashMap, HashSet}, future::{self, Future}, }; @@ -17,7 +17,8 @@ use noirc_frontend::{ FunctionReturnType, Ident, IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, PathKind, PathSegment, Pattern, - Statement, TraitImplItem, TraitItem, TypeImpl, UnresolvedType, UseTree, UseTreeKind, + Statement, TraitImplItem, TraitItem, TypeImpl, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedType, UseTree, UseTreeKind, }, graph::{CrateId, Dependency}, hir::{ @@ -99,6 +100,7 @@ struct NodeFinder<'a> { dependencies: &'a Vec, interner: &'a NodeInterner, local_variables: HashMap, + type_parameters: HashSet, } impl<'a> NodeFinder<'a> { @@ -122,7 +124,6 @@ impl<'a> NodeFinder<'a> { def_map.root() }; let module_id = ModuleId { krate, local_id }; - let local_variables = HashMap::new(); Self { file, byte_index, @@ -132,7 +133,8 @@ impl<'a> NodeFinder<'a> { def_maps, dependencies, interner, - local_variables, + local_variables: HashMap::new(), + type_parameters: HashSet::new(), } } @@ -196,13 +198,24 @@ impl<'a> NodeFinder<'a> { &mut self, noir_function: &NoirFunction, ) -> Option { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&noir_function.def.generics); + + let mut response = None; for param in &noir_function.def.parameters { - if let Some(response) = self.find_in_unresolved_type(¶m.typ) { - return Some(response); + response = self.find_in_unresolved_type(¶m.typ); + if response.is_some() { + break; } } + if let Some(response) = response { + self.type_parameters.clear(); + return Some(response); + } + if let Some(response) = self.find_in_function_return_type(&noir_function.def.return_type) { + self.type_parameters.clear(); return Some(response); } @@ -211,19 +224,31 @@ impl<'a> NodeFinder<'a> { self.collect_local_variables(¶m.pattern); } - self.find_in_block_expression(&noir_function.def.body) + let response = self.find_in_block_expression(&noir_function.def.body); + + self.type_parameters.clear(); + + response } fn find_in_noir_trait_impl( &mut self, noir_trait_impl: &NoirTraitImpl, ) -> Option { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&noir_trait_impl.impl_generics); + + let mut response = None; for item in &noir_trait_impl.items { - if let Some(completion) = self.find_in_trait_impl_item(item) { - return Some(completion); + response = self.find_in_trait_impl_item(item); + if response.is_some() { + break; } } - None + + self.type_parameters.clear(); + + response } fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) -> Option { @@ -235,13 +260,20 @@ impl<'a> NodeFinder<'a> { } fn find_in_type_impl(&mut self, type_impl: &TypeImpl) -> Option { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&type_impl.generics); + + let mut response = None; for (method, _) in &type_impl.methods { - if let Some(completion) = self.find_in_noir_function(method) { - return Some(completion); + response = self.find_in_noir_function(method); + if response.is_some() { + break; } } - None + self.type_parameters.clear(); + + response } fn find_in_noir_type_alias( @@ -252,13 +284,20 @@ impl<'a> NodeFinder<'a> { } fn find_in_noir_struct(&mut self, noir_struct: &NoirStruct) -> Option { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&noir_struct.generics); + + let mut response = None; for (_name, unresolved_type) in &noir_struct.fields { - if let Some(response) = self.find_in_unresolved_type(unresolved_type) { - return Some(response); + response = self.find_in_unresolved_type(unresolved_type); + if response.is_some() { + break; } } - None + self.type_parameters.clear(); + + response } fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) -> Option { @@ -853,6 +892,9 @@ impl<'a> NodeFinder<'a> { let builtin_types_response = builtin_types_completion(&prefix); let response = merge_completion_responses(response, builtin_types_response); + let type_parameters_response = self.type_parameters_completion(&prefix); + let response = merge_completion_responses(response, type_parameters_response); + response } } @@ -891,6 +933,26 @@ impl<'a> NodeFinder<'a> { } } + fn type_parameters_completion(&self, prefix: &String) -> Option { + let mut completion_items = Vec::new(); + + for name in &self.type_parameters { + if name_matches(name, prefix) { + completion_items.push(simple_completion_item( + name, + CompletionItemKind::TYPE_PARAMETER, + None, + )); + } + } + + if completion_items.is_empty() { + None + } else { + Some(CompletionResponse::Array(completion_items)) + } + } + fn find_in_use_tree( &self, use_tree: &UseTree, @@ -1016,6 +1078,24 @@ impl<'a> NodeFinder<'a> { } } + fn collect_type_parameters_in_generics(&mut self, generics: &UnresolvedGenerics) { + for generic in generics { + self.collect_type_parameters_in_generic(generic) + } + } + + fn collect_type_parameters_in_generic(&mut self, generic: &UnresolvedGeneric) { + match generic { + UnresolvedGeneric::Variable(ident) => { + self.type_parameters.insert(ident.to_string()); + } + UnresolvedGeneric::Numeric { ident, typ: _ } => { + self.type_parameters.insert(ident.to_string()); + } + UnresolvedGeneric::Resolved(..) => (), + }; + } + fn complete_in_module( &self, module_id: ModuleId, @@ -2260,4 +2340,67 @@ mod completion_tests { ) .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_function_type_parameters() { + let src = r#" + fn foo(x: C>|<) {} + "#; + assert_completion( + src, + vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } } From 533ec039f7c5335d88f6a196ab2e2555c46b00c6 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 05:49:41 -0300 Subject: [PATCH 22/37] Add some comments --- tooling/lsp/src/requests/completion.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 0a27ff69c44..62bde538cb4 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -36,21 +36,35 @@ use crate::{utils, LspState}; use super::process_request; +/// 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 FunctionCompleteKind { + // 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, } +/// 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, } @@ -94,12 +108,19 @@ struct NodeFinder<'a> { file: FileId, byte_index: usize, byte: Option, + /// The module ID of the current file. root_module_id: ModuleId, + /// The module ID in scope. This might change as we traverse the AST + /// if we are analyzing something inside an inline module declaration. module_id: ModuleId, def_maps: &'a BTreeMap, dependencies: &'a Vec, interner: &'a NodeInterner, + /// Local variables in the current scope, mapped to their locations. + /// As we traverse the AST, we collect local variables. local_variables: HashMap, + /// Type parameters in the current scope. These are collected when entering + /// a struct, a function, etc., and cleared afterwards. type_parameters: HashSet, } From e256942f83169abdc4f7731a2d08a462acd8fbfd Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 05:49:59 -0300 Subject: [PATCH 23/37] predefined -> builtin --- tooling/lsp/src/requests/completion.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 62bde538cb4..a24dda7b586 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -904,8 +904,8 @@ impl<'a> NodeFinder<'a> { let local_vars_response = self.local_variables_completion(&prefix); let response = merge_completion_responses(response, local_vars_response); - let predefined_response = predefined_functions_completion(&prefix); - let response = merge_completion_responses(response, predefined_response); + let builtin_response = builtin_functions_completion(&prefix); + let response = merge_completion_responses(response, builtin_response); response } @@ -1364,7 +1364,7 @@ fn name_matches(name: &str, prefix: &str) -> bool { name.starts_with(prefix) } -fn predefined_functions_completion(prefix: &String) -> Option { +fn builtin_functions_completion(prefix: &String) -> Option { let mut completion_items = Vec::new(); if name_matches("assert", prefix) { @@ -1926,7 +1926,7 @@ mod completion_tests { } #[test] - async fn test_complete_predefined_functions() { + async fn test_complete_builtin_functions() { let src = r#" fn main() { a>|< From 129b9529b91cc67cb8f9f1e4d568a3bda1502b0c Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 05:59:52 -0300 Subject: [PATCH 24/37] clippy --- tooling/lsp/src/requests/completion.rs | 37 ++++++++++++-------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index a24dda7b586..73a978f43cc 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -422,7 +422,7 @@ impl<'a> NodeFinder<'a> { let old_local_variables = self.local_variables.clone(); self.local_variables.clear(); - let response = self.find_in_statement(&statement); + let response = self.find_in_statement(statement); self.local_variables = old_local_variables; response } @@ -547,7 +547,7 @@ impl<'a> NodeFinder<'a> { self.find_in_expression(&prefix_expression.rhs) } noirc_frontend::ast::ExpressionKind::Index(index_expression) => { - self.find_in_index_expression(&index_expression) + self.find_in_index_expression(index_expression) } noirc_frontend::ast::ExpressionKind::Call(call_expression) => { self.find_in_call_expression(call_expression) @@ -588,7 +588,7 @@ impl<'a> NodeFinder<'a> { let old_local_variables = self.local_variables.clone(); self.local_variables.clear(); - let response = self.find_in_block_expression(&block_expression); + let response = self.find_in_block_expression(block_expression); self.local_variables = old_local_variables; response } @@ -741,7 +741,7 @@ impl<'a> NodeFinder<'a> { let old_local_variables = self.local_variables.clone(); for (pattern, _) in &lambda.parameters { - self.collect_local_variables(pattern) + self.collect_local_variables(pattern); } let response = self.find_in_expression(&lambda.body); @@ -905,18 +905,14 @@ impl<'a> NodeFinder<'a> { let response = merge_completion_responses(response, local_vars_response); let builtin_response = builtin_functions_completion(&prefix); - let response = merge_completion_responses(response, builtin_response); - - response + merge_completion_responses(response, builtin_response) } RequestedItems::OnlyTypes => { let builtin_types_response = builtin_types_completion(&prefix); let response = merge_completion_responses(response, builtin_types_response); let type_parameters_response = self.type_parameters_completion(&prefix); - let response = merge_completion_responses(response, type_parameters_response); - - response + merge_completion_responses(response, type_parameters_response) } } } else { @@ -924,7 +920,7 @@ impl<'a> NodeFinder<'a> { } } - fn local_variables_completion(&self, prefix: &String) -> Option { + fn local_variables_completion(&self, prefix: &str) -> Option { let mut completion_items = Vec::new(); for (name, span) in &self.local_variables { @@ -954,7 +950,7 @@ impl<'a> NodeFinder<'a> { } } - fn type_parameters_completion(&self, prefix: &String) -> Option { + fn type_parameters_completion(&self, prefix: &str) -> Option { let mut completion_items = Vec::new(); for name in &self.type_parameters { @@ -1101,7 +1097,7 @@ impl<'a> NodeFinder<'a> { fn collect_type_parameters_in_generics(&mut self, generics: &UnresolvedGenerics) { for generic in generics { - self.collect_type_parameters_in_generic(generic) + self.collect_type_parameters_in_generic(generic); } } @@ -1117,10 +1113,11 @@ impl<'a> NodeFinder<'a> { }; } + #[allow(clippy::too_many_arguments)] fn complete_in_module( &self, module_id: ModuleId, - prefix: &String, + prefix: &str, path_kind: PathKind, at_root: bool, module_completion_kind: ModuleCompletionKind, @@ -1187,7 +1184,7 @@ impl<'a> NodeFinder<'a> { } } - if name_matches("crate::", &prefix) { + if name_matches("crate::", prefix) { completion_items.push(simple_completion_item( "crate::", CompletionItemKind::KEYWORD, @@ -1195,7 +1192,7 @@ impl<'a> NodeFinder<'a> { )); } - if module_data.parent.is_some() && name_matches("super::", &prefix) { + if module_data.parent.is_some() && name_matches("super::", prefix) { completion_items.push(simple_completion_item( "super::", CompletionItemKind::KEYWORD, @@ -1266,14 +1263,14 @@ impl<'a> NodeFinder<'a> { FunctionCompleteKind::NameAndParameters => { let label = format!("{}(…)", name); let kind = CompletionItemKind::FUNCTION; - let insert_text = self.compute_function_insert_text(&func_meta, &name); + let insert_text = self.compute_function_insert_text(func_meta, &name); snippet_completion_item(label, kind, insert_text, Some(description)) } } } - fn compute_function_insert_text(&self, func_meta: &FuncMeta, name: &String) -> String { + fn compute_function_insert_text(&self, func_meta: &FuncMeta, name: &str) -> String { let mut text = String::new(); text.push_str(name); text.push('('); @@ -1364,7 +1361,7 @@ fn name_matches(name: &str, prefix: &str) -> bool { name.starts_with(prefix) } -fn builtin_functions_completion(prefix: &String) -> Option { +fn builtin_functions_completion(prefix: &str) -> Option { let mut completion_items = Vec::new(); if name_matches("assert", prefix) { @@ -1402,7 +1399,7 @@ fn builtin_functions_completion(prefix: &String) -> Option { } } -fn builtin_types_completion(prefix: &String) -> Option { +fn builtin_types_completion(prefix: &str) -> Option { let mut completion_items = Vec::new(); for typ in [ From 5c9910c344a81b074a9382ffb2ffbc21a9c96426 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 06:20:53 -0300 Subject: [PATCH 25/37] Fix some tests --- tooling/lsp/src/requests/completion.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 73a978f43cc..1b5670a7ac3 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -1807,13 +1807,13 @@ mod completion_tests { #[test] async fn test_complete_path_shows_module() { let src = r#" - mod foo {} + mod foobar {} fn main() { - f>|< + fo>|< } "#; - assert_completion(src, vec![module_completion_item("foo")]).await; + assert_completion(src, vec![module_completion_item("foobar")]).await; } #[test] @@ -2048,7 +2048,7 @@ mod completion_tests { fn SomeFunction() {} struct Another { - some: S>|< + some: So>|< } "#; assert_completion( @@ -2067,7 +2067,7 @@ mod completion_tests { let src = r#" struct Something {} - fn foo(x: S>|<) {} + fn foo(x: So>|<) {} "#; assert_completion( src, @@ -2085,7 +2085,7 @@ mod completion_tests { let src = r#" struct Something {} - fn foo() -> S>|< {} + fn foo() -> So>|< {} "#; assert_completion( src, @@ -2103,7 +2103,7 @@ mod completion_tests { let src = r#" struct Something {} - type Foo = S>|< + type Foo = So>|< "#; assert_completion( src, @@ -2122,7 +2122,7 @@ mod completion_tests { struct Something {} trait Trait { - fn foo(s: S>|<); + fn foo(s: So>|<); } "#; assert_completion( @@ -2142,7 +2142,7 @@ mod completion_tests { struct Something {} trait Trait { - fn foo() -> S>|<; + fn foo() -> So>|<; } "#; assert_completion( @@ -2162,7 +2162,7 @@ mod completion_tests { struct Something {} fn main() { - let x: S>|< + let x: So>|< } "#; assert_completion( @@ -2182,7 +2182,7 @@ mod completion_tests { struct Something {} fn main() { - foo(|s: S>|<| s) + foo(|s: So>|<| s) } "#; assert_completion( From 5e2ba4a9d6c8bf6f9f900e6b9ba28985e7e83adf Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 06:24:25 -0300 Subject: [PATCH 26/37] Fix type parameters being cleared by function --- tooling/lsp/src/requests/completion.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 1b5670a7ac3..2017dc4e390 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -219,7 +219,7 @@ impl<'a> NodeFinder<'a> { &mut self, noir_function: &NoirFunction, ) -> Option { - self.type_parameters.clear(); + let old_type_parameters = self.type_parameters.clone(); self.collect_type_parameters_in_generics(&noir_function.def.generics); let mut response = None; @@ -231,12 +231,12 @@ impl<'a> NodeFinder<'a> { } if let Some(response) = response { - self.type_parameters.clear(); + self.type_parameters = old_type_parameters; return Some(response); } if let Some(response) = self.find_in_function_return_type(&noir_function.def.return_type) { - self.type_parameters.clear(); + self.type_parameters = old_type_parameters; return Some(response); } @@ -247,7 +247,7 @@ impl<'a> NodeFinder<'a> { let response = self.find_in_block_expression(&noir_function.def.body); - self.type_parameters.clear(); + self.type_parameters = old_type_parameters; response } From c1addbc8ff87936ab0f300f68d50431d19b1a361 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 06:29:02 -0300 Subject: [PATCH 27/37] Collect trait function generics --- tooling/lsp/src/requests/completion.rs | 33 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 2017dc4e390..c2bb0a3c38f 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -334,19 +334,24 @@ impl<'a> NodeFinder<'a> { match trait_item { TraitItem::Function { name: _, - generics: _, + generics, parameters, return_type, where_clause, body, } => { + let old_type_parameters = self.type_parameters.clone(); + self.collect_type_parameters_in_generics(generics); + for (_name, unresolved_type) in parameters { if let Some(response) = self.find_in_unresolved_type(unresolved_type) { + self.type_parameters = old_type_parameters; return Some(response); } } if let Some(response) = self.find_in_function_return_type(return_type) { + self.type_parameters = old_type_parameters; return Some(response); } @@ -354,11 +359,12 @@ impl<'a> NodeFinder<'a> { if let Some(response) = self.find_in_unresolved_type(&unresolved_trait_constraint.typ) { + self.type_parameters = old_type_parameters; return Some(response); } } - if let Some(body) = body { + let response = if let Some(body) = body { self.local_variables.clear(); for (name, _) in parameters { self.local_variables.insert(name.to_string(), name.span()); @@ -366,7 +372,11 @@ impl<'a> NodeFinder<'a> { self.find_in_block_expression(body) } else { None - } + }; + + self.type_parameters = old_type_parameters; + + response } TraitItem::Constant { name: _, typ, default_value } => { if let Some(response) = self.find_in_unresolved_type(typ) { @@ -2410,6 +2420,23 @@ mod completion_tests { .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#" From c42b9fdc6cf3d2f78ec7a0a7a60fc4c6fe495cdd Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 08:18:15 -0300 Subject: [PATCH 28/37] Simplify by not returning Option all the time --- tooling/lsp/src/requests/completion.rs | 690 ++++++++----------------- 1 file changed, 220 insertions(+), 470 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index c2bb0a3c38f..7110d11dec9 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -116,6 +116,8 @@ struct NodeFinder<'a> { def_maps: &'a BTreeMap, dependencies: &'a Vec, interner: &'a NodeInterner, + /// Completion items we find along the way. + completion_items: Vec, /// Local variables in the current scope, mapped to their locations. /// As we traverse the AST, we collect local variables. local_variables: HashMap, @@ -154,31 +156,31 @@ impl<'a> NodeFinder<'a> { def_maps, dependencies, interner, + completion_items: Vec::new(), local_variables: HashMap::new(), type_parameters: HashSet::new(), } } fn find(&mut self, parsed_module: &ParsedModule) -> Option { - self.find_in_parsed_module(parsed_module) + self.find_in_parsed_module(parsed_module); + + if self.completion_items.is_empty() { + None + } else { + Some(CompletionResponse::Array(std::mem::take(&mut self.completion_items))) + } } - fn find_in_parsed_module( - &mut self, - parsed_module: &ParsedModule, - ) -> Option { + fn find_in_parsed_module(&mut self, parsed_module: &ParsedModule) { for item in &parsed_module.items { - if let Some(response) = self.find_in_item(item) { - return Some(response); - } + self.find_in_item(item); } - - None } - fn find_in_item(&mut self, item: &Item) -> Option { + fn find_in_item(&mut self, item: &Item) { if !self.includes_span(item.span) { - return None; + return; } match &item.kind { @@ -191,18 +193,18 @@ impl<'a> NodeFinder<'a> { let previous_module_id = self.module_id; let def_map = &self.def_maps[&self.module_id.krate]; - let module_data = def_map.modules().get(self.module_id.local_id.0)?; + let Some(module_data) = def_map.modules().get(self.module_id.local_id.0) else { + return; + }; if let Some(child_module) = module_data.children.get(&parsed_sub_module.name) { self.module_id = ModuleId { krate: self.module_id.krate, local_id: *child_module }; } - let completion = self.find_in_parsed_module(&parsed_sub_module.contents); + self.find_in_parsed_module(&parsed_sub_module.contents); // Restore the old module before continuing self.module_id = previous_module_id; - - completion } ItemKind::Function(noir_function) => self.find_in_noir_function(noir_function), ItemKind::TraitImpl(noir_trait_impl) => self.find_in_noir_trait_impl(noir_trait_impl), @@ -211,126 +213,82 @@ impl<'a> NodeFinder<'a> { ItemKind::TypeAlias(noir_type_alias) => self.find_in_noir_type_alias(noir_type_alias), ItemKind::Struct(noir_struct) => self.find_in_noir_struct(noir_struct), ItemKind::Trait(noir_trait) => self.find_in_noir_trait(noir_trait), - ItemKind::ModuleDecl(_) => None, + ItemKind::ModuleDecl(_) => (), } } - fn find_in_noir_function( - &mut self, - noir_function: &NoirFunction, - ) -> Option { + fn find_in_noir_function(&mut self, noir_function: &NoirFunction) { let old_type_parameters = self.type_parameters.clone(); self.collect_type_parameters_in_generics(&noir_function.def.generics); - let mut response = None; for param in &noir_function.def.parameters { - response = self.find_in_unresolved_type(¶m.typ); - if response.is_some() { - break; - } + self.find_in_unresolved_type(¶m.typ); } - if let Some(response) = response { - self.type_parameters = old_type_parameters; - return Some(response); - } - - if let Some(response) = self.find_in_function_return_type(&noir_function.def.return_type) { - self.type_parameters = old_type_parameters; - return Some(response); - } + self.find_in_function_return_type(&noir_function.def.return_type); self.local_variables.clear(); for param in &noir_function.def.parameters { self.collect_local_variables(¶m.pattern); } - let response = self.find_in_block_expression(&noir_function.def.body); + self.find_in_block_expression(&noir_function.def.body); self.type_parameters = old_type_parameters; - - response } - fn find_in_noir_trait_impl( - &mut self, - noir_trait_impl: &NoirTraitImpl, - ) -> Option { + fn find_in_noir_trait_impl(&mut self, noir_trait_impl: &NoirTraitImpl) { self.type_parameters.clear(); self.collect_type_parameters_in_generics(&noir_trait_impl.impl_generics); - let mut response = None; for item in &noir_trait_impl.items { - response = self.find_in_trait_impl_item(item); - if response.is_some() { - break; - } + self.find_in_trait_impl_item(item); } self.type_parameters.clear(); - - response } - fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) -> Option { + fn find_in_trait_impl_item(&mut self, item: &TraitImplItem) { match item { TraitImplItem::Function(noir_function) => self.find_in_noir_function(noir_function), - TraitImplItem::Constant(_, _, _) => None, - TraitImplItem::Type { .. } => None, + TraitImplItem::Constant(_, _, _) => (), + TraitImplItem::Type { .. } => (), } } - fn find_in_type_impl(&mut self, type_impl: &TypeImpl) -> Option { + fn find_in_type_impl(&mut self, type_impl: &TypeImpl) { self.type_parameters.clear(); self.collect_type_parameters_in_generics(&type_impl.generics); - let mut response = None; for (method, _) in &type_impl.methods { - response = self.find_in_noir_function(method); - if response.is_some() { - break; - } + self.find_in_noir_function(method); } self.type_parameters.clear(); - - response } - fn find_in_noir_type_alias( - &mut self, - noir_type_alias: &NoirTypeAlias, - ) -> Option { + fn find_in_noir_type_alias(&mut self, noir_type_alias: &NoirTypeAlias) { self.find_in_unresolved_type(&noir_type_alias.typ) } - fn find_in_noir_struct(&mut self, noir_struct: &NoirStruct) -> Option { + fn find_in_noir_struct(&mut self, noir_struct: &NoirStruct) { self.type_parameters.clear(); self.collect_type_parameters_in_generics(&noir_struct.generics); - let mut response = None; for (_name, unresolved_type) in &noir_struct.fields { - response = self.find_in_unresolved_type(unresolved_type); - if response.is_some() { - break; - } + self.find_in_unresolved_type(unresolved_type); } self.type_parameters.clear(); - - response } - fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) -> Option { + fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { for item in &noir_trait.items { - if let Some(response) = self.find_in_trait_item(item) { - return Some(response); - } + self.find_in_trait_item(item); } - None } - fn find_in_trait_item(&mut self, trait_item: &TraitItem) -> Option { + fn find_in_trait_item(&mut self, trait_item: &TraitItem) { match trait_item { TraitItem::Function { name: _, @@ -344,73 +302,45 @@ impl<'a> NodeFinder<'a> { self.collect_type_parameters_in_generics(generics); for (_name, unresolved_type) in parameters { - if let Some(response) = self.find_in_unresolved_type(unresolved_type) { - self.type_parameters = old_type_parameters; - return Some(response); - } + self.find_in_unresolved_type(unresolved_type); } - if let Some(response) = self.find_in_function_return_type(return_type) { - self.type_parameters = old_type_parameters; - return Some(response); - } + self.find_in_function_return_type(return_type); for unresolved_trait_constraint in where_clause { - if let Some(response) = - self.find_in_unresolved_type(&unresolved_trait_constraint.typ) - { - self.type_parameters = old_type_parameters; - return Some(response); - } + self.find_in_unresolved_type(&unresolved_trait_constraint.typ); } - let response = if let Some(body) = body { + if let Some(body) = body { self.local_variables.clear(); for (name, _) in parameters { self.local_variables.insert(name.to_string(), name.span()); } self.find_in_block_expression(body) - } else { - None }; self.type_parameters = old_type_parameters; - - response } TraitItem::Constant { name: _, typ, default_value } => { - if let Some(response) = self.find_in_unresolved_type(typ) { - return Some(response); - } + self.find_in_unresolved_type(typ); if let Some(default_value) = default_value { self.find_in_expression(default_value) - } else { - None } } - TraitItem::Type { name: _ } => None, + TraitItem::Type { name: _ } => (), } } - fn find_in_block_expression( - &mut self, - block_expression: &BlockExpression, - ) -> Option { + fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { let old_local_variables = self.local_variables.clone(); - let mut completion = None; for statement in &block_expression.statements { - completion = self.find_in_statement(statement); - if completion.is_some() { - break; - } + self.find_in_statement(statement); } self.local_variables = old_local_variables; - - completion } - fn find_in_statement(&mut self, statement: &Statement) -> Option { + fn find_in_statement(&mut self, statement: &Statement) { match &statement.kind { noirc_frontend::ast::StatementKind::Let(let_statement) => { self.find_in_let_statement(let_statement, true) @@ -432,16 +362,16 @@ impl<'a> NodeFinder<'a> { let old_local_variables = self.local_variables.clone(); self.local_variables.clear(); - let response = self.find_in_statement(statement); + self.find_in_statement(statement); + self.local_variables = old_local_variables; - response } noirc_frontend::ast::StatementKind::Semi(expression) => { self.find_in_expression(expression) } noirc_frontend::ast::StatementKind::Break | noirc_frontend::ast::StatementKind::Continue - | noirc_frontend::ast::StatementKind::Error => None, + | noirc_frontend::ast::StatementKind::Error => (), } } @@ -449,105 +379,71 @@ impl<'a> NodeFinder<'a> { &mut self, let_statement: &LetStatement, collect_local_variables: bool, - ) -> Option { - if let Some(response) = self.find_in_unresolved_type(&let_statement.r#type) { - return Some(response); - } - - if let Some(response) = self.find_in_expression(&let_statement.expression) { - return Some(response); - } + ) { + self.find_in_unresolved_type(&let_statement.r#type); + self.find_in_expression(&let_statement.expression); if collect_local_variables { self.collect_local_variables(&let_statement.pattern); } - - None } - fn find_in_constrain_statement( - &mut self, - constrain_statement: &ConstrainStatement, - ) -> Option { - if let Some(response) = self.find_in_expression(&constrain_statement.0) { - return Some(response); - } + fn find_in_constrain_statement(&mut self, constrain_statement: &ConstrainStatement) { + self.find_in_expression(&constrain_statement.0); if let Some(exp) = &constrain_statement.1 { self.find_in_expression(exp) - } else { - None } } fn find_in_assign_statement( &mut self, assign_statement: &noirc_frontend::ast::AssignStatement, - ) -> Option { - if let Some(response) = self.find_in_lvalue(&assign_statement.lvalue) { - return Some(response); - } - + ) { + self.find_in_lvalue(&assign_statement.lvalue); self.find_in_expression(&assign_statement.expression) } - fn find_in_for_loop_statement( - &mut self, - for_loop_statement: &ForLoopStatement, - ) -> Option { + fn find_in_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) { let old_local_variables = self.local_variables.clone(); let ident = &for_loop_statement.identifier; self.local_variables.insert(ident.to_string(), ident.span()); - if let Some(response) = self.find_in_for_range(&for_loop_statement.range) { - return Some(response); - } - - let response = self.find_in_expression(&for_loop_statement.block); + self.find_in_for_range(&for_loop_statement.range); + self.find_in_expression(&for_loop_statement.block); self.local_variables = old_local_variables; - - response } - fn find_in_lvalue(&mut self, lvalue: &LValue) -> Option { + fn find_in_lvalue(&mut self, lvalue: &LValue) { match lvalue { - LValue::Ident(_) => None, + LValue::Ident(_) => (), LValue::MemberAccess { object, field_name: _, span: _ } => self.find_in_lvalue(object), LValue::Index { array, index, span: _ } => { - if let Some(response) = self.find_in_lvalue(array) { - return Some(response); - } - - self.find_in_expression(index) + self.find_in_lvalue(array); + self.find_in_expression(index); } LValue::Dereference(lvalue, _) => self.find_in_lvalue(lvalue), } } - fn find_in_for_range(&mut self, for_range: &ForRange) -> Option { + fn find_in_for_range(&mut self, for_range: &ForRange) { match for_range { ForRange::Range(start, end) => { - if let Some(response) = self.find_in_expression(start) { - return Some(response); - } - - self.find_in_expression(end) + self.find_in_expression(start); + self.find_in_expression(end); } ForRange::Array(expression) => self.find_in_expression(expression), } } - fn find_in_expressions(&mut self, expressions: &[Expression]) -> Option { + fn find_in_expressions(&mut self, expressions: &[Expression]) { for expression in expressions { - if let Some(response) = self.find_in_expression(expression) { - return Some(response); - } + self.find_in_expression(expression); } - None } - fn find_in_expression(&mut self, expression: &Expression) -> Option { + fn find_in_expression(&mut self, expression: &Expression) { match &expression.kind { noirc_frontend::ast::ExpressionKind::Literal(literal) => self.find_in_literal(literal), noirc_frontend::ast::ExpressionKind::Block(block_expression) => { @@ -598,20 +494,20 @@ impl<'a> NodeFinder<'a> { let old_local_variables = self.local_variables.clone(); self.local_variables.clear(); - let response = self.find_in_block_expression(block_expression); + self.find_in_block_expression(block_expression); + self.local_variables = old_local_variables; - response } noirc_frontend::ast::ExpressionKind::AsTraitPath(as_trait_path) => { self.find_in_as_trait_path(as_trait_path) } noirc_frontend::ast::ExpressionKind::Quote(_) | noirc_frontend::ast::ExpressionKind::Resolved(_) - | noirc_frontend::ast::ExpressionKind::Error => None, + | noirc_frontend::ast::ExpressionKind::Error => (), } } - fn find_in_literal(&mut self, literal: &Literal) -> Option { + fn find_in_literal(&mut self, literal: &Literal) { match literal { Literal::Array(array_literal) => self.find_in_array_literal(array_literal), Literal::Slice(array_literal) => self.find_in_array_literal(array_literal), @@ -620,133 +516,76 @@ impl<'a> NodeFinder<'a> { | Literal::Str(_) | Literal::RawStr(_, _) | Literal::FmtStr(_) - | Literal::Unit => None, + | Literal::Unit => (), } } - fn find_in_array_literal( - &mut self, - array_literal: &ArrayLiteral, - ) -> Option { + fn find_in_array_literal(&mut self, array_literal: &ArrayLiteral) { match array_literal { ArrayLiteral::Standard(expressions) => self.find_in_expressions(expressions), ArrayLiteral::Repeated { repeated_element, length } => { - if let Some(completion) = self.find_in_expression(repeated_element) { - return Some(completion); - } - - self.find_in_expression(length) + self.find_in_expression(repeated_element); + self.find_in_expression(length); } } } - fn find_in_index_expression( - &mut self, - index_expression: &IndexExpression, - ) -> Option { - if let Some(response) = self.find_in_expression(&index_expression.collection) { - return Some(response); - } - - self.find_in_expression(&index_expression.index) + fn find_in_index_expression(&mut self, index_expression: &IndexExpression) { + self.find_in_expression(&index_expression.collection); + self.find_in_expression(&index_expression.index); } - fn find_in_call_expression( - &mut self, - call_expression: &CallExpression, - ) -> Option { - if let Some(response) = self.find_in_expression(&call_expression.func) { - return Some(response); - } - - self.find_in_expressions(&call_expression.arguments) + fn find_in_call_expression(&mut self, call_expression: &CallExpression) { + self.find_in_expression(&call_expression.func); + self.find_in_expressions(&call_expression.arguments); } - fn find_in_method_call_expression( - &mut self, - method_call_expression: &MethodCallExpression, - ) -> Option { - if let Some(response) = self.find_in_expression(&method_call_expression.object) { - return Some(response); - } - - self.find_in_expressions(&method_call_expression.arguments) + 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); } - fn find_in_constructor_expression( - &mut self, - constructor_expression: &ConstructorExpression, - ) -> Option { - if let Some(response) = - self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes) - { - return Some(response); - } + fn find_in_constructor_expression(&mut self, constructor_expression: &ConstructorExpression) { + self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); for (_field_name, expression) in &constructor_expression.fields { - if let Some(response) = self.find_in_expression(expression) { - return Some(response); - } + self.find_in_expression(expression); } - - None } fn find_in_member_access_expression( &mut self, member_access_expression: &MemberAccessExpression, - ) -> Option { + ) { self.find_in_expression(&member_access_expression.lhs) } - fn find_in_cast_expression( - &mut self, - cast_expression: &CastExpression, - ) -> Option { + fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { self.find_in_expression(&cast_expression.lhs) } - fn find_in_infix_expression( - &mut self, - infix_expression: &InfixExpression, - ) -> Option { - if let Some(response) = self.find_in_expression(&infix_expression.lhs) { - return Some(response); - } - + fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { + self.find_in_expression(&infix_expression.lhs); self.find_in_expression(&infix_expression.rhs) } - fn find_in_if_expression( - &mut self, - if_expression: &IfExpression, - ) -> Option { - if let Some(response) = self.find_in_expression(&if_expression.condition) { - return Some(response); - } + fn find_in_if_expression(&mut self, if_expression: &IfExpression) { + self.find_in_expression(&if_expression.condition); let old_local_variables = self.local_variables.clone(); - let response = self.find_in_expression(&if_expression.consequence); + self.find_in_expression(&if_expression.consequence); self.local_variables = old_local_variables; - if let Some(response) = response { - return Some(response); - } - let old_local_variables = self.local_variables.clone(); - let response = if let Some(alternative) = &if_expression.alternative { - self.find_in_expression(alternative) - } else { - None - }; - self.local_variables = old_local_variables; - response + if let Some(alternative) = &if_expression.alternative { + let old_local_variables = self.local_variables.clone(); + self.find_in_expression(alternative); + self.local_variables = old_local_variables; + } } - fn find_in_lambda(&mut self, lambda: &Lambda) -> Option { + fn find_in_lambda(&mut self, lambda: &Lambda) { for (_, unresolved_type) in &lambda.parameters { - if let Some(response) = self.find_in_unresolved_type(unresolved_type) { - return Some(response); - } + self.find_in_unresolved_type(unresolved_type); } let old_local_variables = self.local_variables.clone(); @@ -754,49 +593,34 @@ impl<'a> NodeFinder<'a> { self.collect_local_variables(pattern); } - let response = self.find_in_expression(&lambda.body); + self.find_in_expression(&lambda.body); self.local_variables = old_local_variables; - - response } - fn find_in_as_trait_path(&mut self, as_trait_path: &AsTraitPath) -> Option { + fn find_in_as_trait_path(&mut self, as_trait_path: &AsTraitPath) { self.find_in_path(&as_trait_path.trait_path, RequestedItems::OnlyTypes) } - fn find_in_function_return_type( - &mut self, - return_type: &FunctionReturnType, - ) -> Option { + fn find_in_function_return_type(&mut self, return_type: &FunctionReturnType) { match return_type { - noirc_frontend::ast::FunctionReturnType::Default(_) => None, + noirc_frontend::ast::FunctionReturnType::Default(_) => (), noirc_frontend::ast::FunctionReturnType::Ty(unresolved_type) => { self.find_in_unresolved_type(unresolved_type) } } } - fn find_in_unresolved_types( - &mut self, - unresolved_type: &[UnresolvedType], - ) -> Option { + fn find_in_unresolved_types(&mut self, unresolved_type: &[UnresolvedType]) { for unresolved_type in unresolved_type { - if let Some(response) = self.find_in_unresolved_type(unresolved_type) { - return Some(response); - } + self.find_in_unresolved_type(unresolved_type); } - - None } - fn find_in_unresolved_type( - &mut self, - unresolved_type: &UnresolvedType, - ) -> Option { + fn find_in_unresolved_type(&mut self, unresolved_type: &UnresolvedType) { if let Some(span) = unresolved_type.span { if !self.includes_span(span) { - return None; + return; } } @@ -811,17 +635,11 @@ impl<'a> NodeFinder<'a> { self.find_in_unresolved_type(unresolved_type) } noirc_frontend::ast::UnresolvedTypeData::Named(path, unresolved_types, _) => { - if let Some(response) = self.find_in_path(path, RequestedItems::OnlyTypes) { - return Some(response); - } - + self.find_in_path(path, RequestedItems::OnlyTypes); self.find_in_unresolved_types(unresolved_types) } noirc_frontend::ast::UnresolvedTypeData::TraitAsType(path, unresolved_types) => { - if let Some(response) = self.find_in_path(path, RequestedItems::OnlyTypes) { - return Some(response); - } - + self.find_in_path(path, RequestedItems::OnlyTypes); self.find_in_unresolved_types(unresolved_types) } noirc_frontend::ast::UnresolvedTypeData::MutableReference(unresolved_type) => { @@ -831,14 +649,8 @@ impl<'a> NodeFinder<'a> { self.find_in_unresolved_types(unresolved_types) } noirc_frontend::ast::UnresolvedTypeData::Function(args, ret, env) => { - if let Some(response) = self.find_in_unresolved_types(args) { - return Some(response); - } - - if let Some(response) = self.find_in_unresolved_type(ret) { - return Some(response); - } - + self.find_in_unresolved_types(args); + self.find_in_unresolved_type(ret); self.find_in_unresolved_type(env) } noirc_frontend::ast::UnresolvedTypeData::AsTraitPath(as_trait_path) => { @@ -854,18 +666,14 @@ impl<'a> NodeFinder<'a> { | noirc_frontend::ast::UnresolvedTypeData::Bool | noirc_frontend::ast::UnresolvedTypeData::Unit | noirc_frontend::ast::UnresolvedTypeData::Resolved(_) - | noirc_frontend::ast::UnresolvedTypeData::Error => None, + | noirc_frontend::ast::UnresolvedTypeData::Error => (), } } - fn find_in_path( - &mut self, - path: &Path, - requested_items: RequestedItems, - ) -> Option { + 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 { - return None; + return; } let after_colons = self.byte == Some(b':'); @@ -888,7 +696,7 @@ impl<'a> NodeFinder<'a> { let module_id = if idents.is_empty() { Some(self.module_id) } else { self.resolve_module(idents) }; let Some(module_id) = module_id else { - return None; + return; }; let module_completion_kind = if after_colons { @@ -898,7 +706,7 @@ impl<'a> NodeFinder<'a> { }; let function_completion_kind = FunctionCompleteKind::NameAndParameters; - let response = self.complete_in_module( + self.complete_in_module( module_id, &prefix, path.kind, @@ -911,28 +719,18 @@ impl<'a> NodeFinder<'a> { if is_single_segment { match requested_items { RequestedItems::AnyItems => { - let local_vars_response = self.local_variables_completion(&prefix); - let response = merge_completion_responses(response, local_vars_response); - - let builtin_response = builtin_functions_completion(&prefix); - merge_completion_responses(response, builtin_response) + self.local_variables_completion(&prefix); + self.builtin_functions_completion(&prefix); } RequestedItems::OnlyTypes => { - let builtin_types_response = builtin_types_completion(&prefix); - let response = merge_completion_responses(response, builtin_types_response); - - let type_parameters_response = self.type_parameters_completion(&prefix); - merge_completion_responses(response, type_parameters_response) + self.builtin_types_completion(&prefix); + self.type_parameters_completion(&prefix); } } - } else { - response } } - fn local_variables_completion(&self, prefix: &str) -> Option { - let mut completion_items = Vec::new(); - + fn local_variables_completion(&mut self, prefix: &str) { for (name, span) in &self.local_variables { if name_matches(name, prefix) { let location = Location::new(*span, self.file); @@ -945,46 +743,28 @@ impl<'a> NodeFinder<'a> { None }; - completion_items.push(simple_completion_item( + self.completion_items.push(simple_completion_item( name, CompletionItemKind::VARIABLE, description, )); } } - - if completion_items.is_empty() { - None - } else { - Some(CompletionResponse::Array(completion_items)) - } } - fn type_parameters_completion(&self, prefix: &str) -> Option { - let mut completion_items = Vec::new(); - + fn type_parameters_completion(&mut self, prefix: &str) { for name in &self.type_parameters { if name_matches(name, prefix) { - completion_items.push(simple_completion_item( + self.completion_items.push(simple_completion_item( name, CompletionItemKind::TYPE_PARAMETER, None, )); } } - - if completion_items.is_empty() { - None - } else { - Some(CompletionResponse::Array(completion_items)) - } } - fn find_in_use_tree( - &self, - use_tree: &UseTree, - prefixes: &mut Vec, - ) -> Option { + fn find_in_use_tree(&mut self, use_tree: &UseTree, prefixes: &mut Vec) { match &use_tree.kind { UseTreeKind::Path(ident, alias) => { prefixes.push(use_tree.prefix.clone()); @@ -995,25 +775,22 @@ impl<'a> NodeFinder<'a> { UseTreeKind::List(use_trees) => { prefixes.push(use_tree.prefix.clone()); for use_tree in use_trees { - if let Some(completion) = self.find_in_use_tree(use_tree, prefixes) { - return Some(completion); - } + self.find_in_use_tree(use_tree, prefixes); } prefixes.pop(); - None } } } fn find_in_use_tree_path( - &self, + &mut self, prefixes: &Vec, ident: &Ident, alias: &Option, - ) -> Option { + ) { if let Some(_alias) = alias { // Won't handle completion if there's an alias (for now) - return None; + return; } let after_colons = self.byte == Some(b':'); @@ -1022,7 +799,7 @@ impl<'a> NodeFinder<'a> { after_colons && self.byte_index - 2 == ident.span().end() as usize; if !(at_ident_end || at_ident_colons_end) { - return None; + return; } let path_kind = prefixes[0].kind; @@ -1042,7 +819,7 @@ impl<'a> NodeFinder<'a> { // We are right after "::" segments.push(ident.clone()); - self.resolve_module(segments).and_then(|module_id| { + if let Some(module_id) = self.resolve_module(segments) { let prefix = String::new(); let at_root = false; self.complete_in_module( @@ -1053,8 +830,8 @@ impl<'a> NodeFinder<'a> { module_completion_kind, function_completion_kind, requested_items, - ) - }) + ); + }; } else { // We are right after the last segment let prefix = ident.to_string(); @@ -1068,10 +845,10 @@ impl<'a> NodeFinder<'a> { module_completion_kind, function_completion_kind, requested_items, - ) + ); } else { - let at_root = false; - self.resolve_module(segments).and_then(|module_id| { + if let Some(module_id) = self.resolve_module(segments) { + let at_root = false; self.complete_in_module( module_id, &prefix, @@ -1080,8 +857,8 @@ impl<'a> NodeFinder<'a> { module_completion_kind, function_completion_kind, requested_items, - ) - }) + ); + }; } } } @@ -1125,7 +902,7 @@ impl<'a> NodeFinder<'a> { #[allow(clippy::too_many_arguments)] fn complete_in_module( - &self, + &mut self, module_id: ModuleId, prefix: &str, path_kind: PathKind, @@ -1133,25 +910,34 @@ impl<'a> NodeFinder<'a> { module_completion_kind: ModuleCompletionKind, function_completion_kind: FunctionCompleteKind, requested_items: RequestedItems, - ) -> Option { + ) { let def_map = &self.def_maps[&module_id.krate]; - let mut module_data = def_map.modules().get(module_id.local_id.0)?; + let Some(mut module_data) = def_map.modules().get(module_id.local_id.0) else { + return; + }; if at_root { match path_kind { PathKind::Crate => { - module_data = def_map.modules().get(def_map.root().0)?; + let Some(root_module_data) = def_map.modules().get(def_map.root().0) else { + return; + }; + module_data = root_module_data; } PathKind::Super => { - module_data = def_map.modules().get(module_data.parent?.0)?; + let Some(parent) = module_data.parent else { + return; + }; + let Some(parent_module_data) = def_map.modules().get(parent.0) else { + return; + }; + module_data = parent_module_data; } PathKind::Dep => (), PathKind::Plain => (), } } - let mut completion_items = Vec::new(); - let items = match module_completion_kind { ModuleCompletionKind::DirectChildren => module_data.definitions(), ModuleCompletionKind::AllVisibleItems => module_data.scope(), @@ -1169,7 +955,7 @@ impl<'a> NodeFinder<'a> { function_completion_kind, requested_items, ) { - completion_items.push(completion_item); + self.completion_items.push(completion_item); } } @@ -1180,7 +966,7 @@ impl<'a> NodeFinder<'a> { function_completion_kind, requested_items, ) { - completion_items.push(completion_item); + self.completion_items.push(completion_item); } } } @@ -1190,12 +976,12 @@ impl<'a> NodeFinder<'a> { for dependency in self.dependencies { let dependency_name = dependency.as_name(); if name_matches(&dependency_name, prefix) { - completion_items.push(crate_completion_item(dependency_name)); + self.completion_items.push(crate_completion_item(dependency_name)); } } if name_matches("crate::", prefix) { - completion_items.push(simple_completion_item( + self.completion_items.push(simple_completion_item( "crate::", CompletionItemKind::KEYWORD, None, @@ -1203,19 +989,13 @@ impl<'a> NodeFinder<'a> { } if module_data.parent.is_some() && name_matches("super::", prefix) { - completion_items.push(simple_completion_item( + self.completion_items.push(simple_completion_item( "super::", CompletionItemKind::KEYWORD, None, )); } } - - if completion_items.is_empty() { - None - } else { - Some(CompletionResponse::Array(completion_items)) - } } fn module_def_id_completion_item( @@ -1362,92 +1142,76 @@ impl<'a> NodeFinder<'a> { } } - fn includes_span(&self, span: Span) -> bool { - span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize - } -} - -fn name_matches(name: &str, prefix: &str) -> bool { - name.starts_with(prefix) -} - -fn builtin_functions_completion(prefix: &str) -> Option { - let mut completion_items = Vec::new(); - - if name_matches("assert", prefix) { - completion_items.push(snippet_completion_item( - "assert(…)", - CompletionItemKind::FUNCTION, - "assert(${1:predicate})", - Some("fn(T)".to_string()), - )); - } - - if name_matches("assert_eq", prefix) { - completion_items.push(snippet_completion_item( - "assert_eq(…)", - CompletionItemKind::FUNCTION, - "assert_eq(${1:lhs}, ${2:rhs})", - Some("fn(T, T)".to_string()), - )); - } + fn builtin_functions_completion(&mut self, prefix: &str) { + if name_matches("assert", prefix) { + self.completion_items.push(snippet_completion_item( + "assert(…)", + CompletionItemKind::FUNCTION, + "assert(${1:predicate})", + Some("fn(T)".to_string()), + )); + } - for keyword in ["false", "true"] { - if name_matches(keyword, prefix) { - completion_items.push(simple_completion_item( - keyword, - CompletionItemKind::KEYWORD, - Some("bool".to_string()), + if name_matches("assert_eq", prefix) { + self.completion_items.push(snippet_completion_item( + "assert_eq(…)", + CompletionItemKind::FUNCTION, + "assert_eq(${1:lhs}, ${2:rhs})", + Some("fn(T, T)".to_string()), )); } - } - if completion_items.is_empty() { - None - } else { - Some(CompletionResponse::Array(completion_items)) + 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(prefix: &str) -> Option { - let mut completion_items = Vec::new(); - - for typ in [ - "bool", - "i8", - "i16", - "i32", - "i64", - "u8", - "u16", - "u32", - "u64", - "str", - "Expr", - "Field", - "FunctionDefinition", - "Quoted", - "StructDefinition", - "TraitConstraint", - "TraitDefinition", - "Type", - ] { - if name_matches(typ, prefix) { - completion_items.push(simple_completion_item( - typ, - CompletionItemKind::STRUCT, - Some(typ.to_string()), - )); + fn builtin_types_completion(&mut self, prefix: &str) { + for typ in [ + "bool", + "i8", + "i16", + "i32", + "i64", + "u8", + "u16", + "u32", + "u64", + "str", + "Expr", + "Field", + "FunctionDefinition", + "Quoted", + "StructDefinition", + "TraitConstraint", + "TraitDefinition", + "Type", + ] { + if name_matches(typ, prefix) { + self.completion_items.push(simple_completion_item( + typ, + CompletionItemKind::STRUCT, + Some(typ.to_string()), + )); + } } } - if completion_items.is_empty() { - None - } else { - Some(CompletionResponse::Array(completion_items)) + fn includes_span(&self, span: Span) -> bool { + span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize } } +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) } @@ -1511,20 +1275,6 @@ fn snippet_completion_item( } } -fn merge_completion_responses( - response1: Option, - response2: Option, -) -> Option { - match (response1, response2) { - (Some(CompletionResponse::Array(mut items1)), Some(CompletionResponse::Array(items2))) => { - items1.extend(items2); - Some(CompletionResponse::Array(items1)) - } - (Some(response), None) | (None, Some(response)) => Some(response), - _ => None, - } -} - #[cfg(test)] mod completion_tests { use crate::{notifications::on_did_open_text_document, test_utils}; From d4b18149040c7b98e70cb20fc772a053c0ebf9ee Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 08:20:13 -0300 Subject: [PATCH 29/37] Don't look past the cursor's position --- tooling/lsp/src/requests/completion.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 7110d11dec9..83e4f62b0e2 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -260,8 +260,13 @@ impl<'a> NodeFinder<'a> { self.type_parameters.clear(); self.collect_type_parameters_in_generics(&type_impl.generics); - for (method, _) in &type_impl.methods { + for (method, span) in &type_impl.methods { self.find_in_noir_function(method); + + // Optimization: stop looking in functions past the completion cursor + if span.end() as usize > self.byte_index { + break; + } } self.type_parameters.clear(); @@ -336,6 +341,11 @@ impl<'a> NodeFinder<'a> { let old_local_variables = self.local_variables.clone(); for statement in &block_expression.statements { self.find_in_statement(statement); + + // Optimization: stop looking in statements past the completion cursor + if statement.span.end() as usize > self.byte_index { + break; + } } self.local_variables = old_local_variables; } From b85b889c882e0471d6cade6cb298f3fb4e25a7e1 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 08:29:01 -0300 Subject: [PATCH 30/37] clippy --- tooling/lsp/src/requests/completion.rs | 79 +++++++++++++------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 83e4f62b0e2..83e05d02e43 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -186,7 +186,7 @@ impl<'a> NodeFinder<'a> { match &item.kind { ItemKind::Import(use_tree) => { let mut prefixes = Vec::new(); - self.find_in_use_tree(use_tree, &mut prefixes) + self.find_in_use_tree(use_tree, &mut prefixes); } ItemKind::Submodules(parsed_sub_module) => { // Switch `self.module_id` to the submodule @@ -353,19 +353,19 @@ impl<'a> NodeFinder<'a> { fn find_in_statement(&mut self, statement: &Statement) { match &statement.kind { noirc_frontend::ast::StatementKind::Let(let_statement) => { - self.find_in_let_statement(let_statement, true) + self.find_in_let_statement(let_statement, true); } noirc_frontend::ast::StatementKind::Constrain(constrain_statement) => { - self.find_in_constrain_statement(constrain_statement) + self.find_in_constrain_statement(constrain_statement); } noirc_frontend::ast::StatementKind::Expression(expression) => { - self.find_in_expression(expression) + self.find_in_expression(expression); } noirc_frontend::ast::StatementKind::Assign(assign_statement) => { - self.find_in_assign_statement(assign_statement) + self.find_in_assign_statement(assign_statement); } noirc_frontend::ast::StatementKind::For(for_loop_statement) => { - self.find_in_for_loop_statement(for_loop_statement) + self.find_in_for_loop_statement(for_loop_statement); } noirc_frontend::ast::StatementKind::Comptime(statement) => { // When entering a comptime block, regular local variables shouldn't be offered anymore @@ -377,7 +377,7 @@ impl<'a> NodeFinder<'a> { self.local_variables = old_local_variables; } noirc_frontend::ast::StatementKind::Semi(expression) => { - self.find_in_expression(expression) + self.find_in_expression(expression); } noirc_frontend::ast::StatementKind::Break | noirc_frontend::ast::StatementKind::Continue @@ -402,7 +402,7 @@ impl<'a> NodeFinder<'a> { self.find_in_expression(&constrain_statement.0); if let Some(exp) = &constrain_statement.1 { - self.find_in_expression(exp) + self.find_in_expression(exp); } } @@ -411,7 +411,7 @@ impl<'a> NodeFinder<'a> { assign_statement: &noirc_frontend::ast::AssignStatement, ) { self.find_in_lvalue(&assign_statement.lvalue); - self.find_in_expression(&assign_statement.expression) + self.find_in_expression(&assign_statement.expression); } fn find_in_for_loop_statement(&mut self, for_loop_statement: &ForLoopStatement) { @@ -457,47 +457,47 @@ impl<'a> NodeFinder<'a> { match &expression.kind { noirc_frontend::ast::ExpressionKind::Literal(literal) => self.find_in_literal(literal), noirc_frontend::ast::ExpressionKind::Block(block_expression) => { - self.find_in_block_expression(block_expression) + self.find_in_block_expression(block_expression); } noirc_frontend::ast::ExpressionKind::Prefix(prefix_expression) => { - self.find_in_expression(&prefix_expression.rhs) + self.find_in_expression(&prefix_expression.rhs); } noirc_frontend::ast::ExpressionKind::Index(index_expression) => { - self.find_in_index_expression(index_expression) + self.find_in_index_expression(index_expression); } noirc_frontend::ast::ExpressionKind::Call(call_expression) => { - self.find_in_call_expression(call_expression) + self.find_in_call_expression(call_expression); } noirc_frontend::ast::ExpressionKind::MethodCall(method_call_expression) => { - self.find_in_method_call_expression(method_call_expression) + self.find_in_method_call_expression(method_call_expression); } noirc_frontend::ast::ExpressionKind::Constructor(constructor_expression) => { - self.find_in_constructor_expression(constructor_expression) + self.find_in_constructor_expression(constructor_expression); } noirc_frontend::ast::ExpressionKind::MemberAccess(member_access_expression) => { - self.find_in_member_access_expression(member_access_expression) + self.find_in_member_access_expression(member_access_expression); } noirc_frontend::ast::ExpressionKind::Cast(cast_expression) => { - self.find_in_cast_expression(cast_expression) + self.find_in_cast_expression(cast_expression); } noirc_frontend::ast::ExpressionKind::Infix(infix_expression) => { - self.find_in_infix_expression(infix_expression) + self.find_in_infix_expression(infix_expression); } noirc_frontend::ast::ExpressionKind::If(if_expression) => { - self.find_in_if_expression(if_expression) + self.find_in_if_expression(if_expression); } noirc_frontend::ast::ExpressionKind::Variable(path) => { - self.find_in_path(path, RequestedItems::AnyItems) + self.find_in_path(path, RequestedItems::AnyItems); } noirc_frontend::ast::ExpressionKind::Tuple(expressions) => { - self.find_in_expressions(expressions) + self.find_in_expressions(expressions); } noirc_frontend::ast::ExpressionKind::Lambda(lambda) => self.find_in_lambda(lambda), noirc_frontend::ast::ExpressionKind::Parenthesized(expression) => { - self.find_in_expression(expression) + self.find_in_expression(expression); } noirc_frontend::ast::ExpressionKind::Unquote(expression) => { - self.find_in_expression(expression) + self.find_in_expression(expression); } noirc_frontend::ast::ExpressionKind::Comptime(block_expression, _) => { // When entering a comptime block, regular local variables shouldn't be offered anymore @@ -509,7 +509,7 @@ impl<'a> NodeFinder<'a> { self.local_variables = old_local_variables; } noirc_frontend::ast::ExpressionKind::AsTraitPath(as_trait_path) => { - self.find_in_as_trait_path(as_trait_path) + self.find_in_as_trait_path(as_trait_path); } noirc_frontend::ast::ExpressionKind::Quote(_) | noirc_frontend::ast::ExpressionKind::Resolved(_) @@ -567,16 +567,16 @@ impl<'a> NodeFinder<'a> { &mut self, member_access_expression: &MemberAccessExpression, ) { - self.find_in_expression(&member_access_expression.lhs) + self.find_in_expression(&member_access_expression.lhs); } fn find_in_cast_expression(&mut self, cast_expression: &CastExpression) { - self.find_in_expression(&cast_expression.lhs) + self.find_in_expression(&cast_expression.lhs); } fn find_in_infix_expression(&mut self, infix_expression: &InfixExpression) { self.find_in_expression(&infix_expression.lhs); - self.find_in_expression(&infix_expression.rhs) + self.find_in_expression(&infix_expression.rhs); } fn find_in_if_expression(&mut self, if_expression: &IfExpression) { @@ -609,14 +609,14 @@ impl<'a> NodeFinder<'a> { } fn find_in_as_trait_path(&mut self, as_trait_path: &AsTraitPath) { - self.find_in_path(&as_trait_path.trait_path, RequestedItems::OnlyTypes) + self.find_in_path(&as_trait_path.trait_path, RequestedItems::OnlyTypes); } fn find_in_function_return_type(&mut self, return_type: &FunctionReturnType) { match return_type { noirc_frontend::ast::FunctionReturnType::Default(_) => (), noirc_frontend::ast::FunctionReturnType::Ty(unresolved_type) => { - self.find_in_unresolved_type(unresolved_type) + self.find_in_unresolved_type(unresolved_type); } } } @@ -636,35 +636,35 @@ impl<'a> NodeFinder<'a> { match &unresolved_type.typ { noirc_frontend::ast::UnresolvedTypeData::Array(_, unresolved_type) => { - self.find_in_unresolved_type(unresolved_type) + self.find_in_unresolved_type(unresolved_type); } noirc_frontend::ast::UnresolvedTypeData::Slice(unresolved_type) => { - self.find_in_unresolved_type(unresolved_type) + self.find_in_unresolved_type(unresolved_type); } noirc_frontend::ast::UnresolvedTypeData::Parenthesized(unresolved_type) => { - self.find_in_unresolved_type(unresolved_type) + self.find_in_unresolved_type(unresolved_type); } noirc_frontend::ast::UnresolvedTypeData::Named(path, unresolved_types, _) => { self.find_in_path(path, RequestedItems::OnlyTypes); - self.find_in_unresolved_types(unresolved_types) + self.find_in_unresolved_types(unresolved_types); } noirc_frontend::ast::UnresolvedTypeData::TraitAsType(path, unresolved_types) => { self.find_in_path(path, RequestedItems::OnlyTypes); - self.find_in_unresolved_types(unresolved_types) + self.find_in_unresolved_types(unresolved_types); } noirc_frontend::ast::UnresolvedTypeData::MutableReference(unresolved_type) => { - self.find_in_unresolved_type(unresolved_type) + self.find_in_unresolved_type(unresolved_type); } noirc_frontend::ast::UnresolvedTypeData::Tuple(unresolved_types) => { - self.find_in_unresolved_types(unresolved_types) + self.find_in_unresolved_types(unresolved_types); } noirc_frontend::ast::UnresolvedTypeData::Function(args, ret, env) => { self.find_in_unresolved_types(args); self.find_in_unresolved_type(ret); - self.find_in_unresolved_type(env) + self.find_in_unresolved_type(env); } noirc_frontend::ast::UnresolvedTypeData::AsTraitPath(as_trait_path) => { - self.find_in_as_trait_path(as_trait_path) + self.find_in_as_trait_path(as_trait_path); } noirc_frontend::ast::UnresolvedTypeData::Expression(_) | noirc_frontend::ast::UnresolvedTypeData::FormatString(_, _) @@ -778,9 +778,8 @@ impl<'a> NodeFinder<'a> { match &use_tree.kind { UseTreeKind::Path(ident, alias) => { prefixes.push(use_tree.prefix.clone()); - let response = self.find_in_use_tree_path(prefixes, ident, alias); + self.find_in_use_tree_path(prefixes, ident, alias); prefixes.pop(); - response } UseTreeKind::List(use_trees) => { prefixes.push(use_tree.prefix.clone()); From b17aa77f508f5f8f592267ee20df641559858c83 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 08:33:04 -0300 Subject: [PATCH 31/37] More clippy --- tooling/lsp/src/requests/completion.rs | 31 +++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 83e05d02e43..b83a4b243e4 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -273,7 +273,7 @@ impl<'a> NodeFinder<'a> { } fn find_in_noir_type_alias(&mut self, noir_type_alias: &NoirTypeAlias) { - self.find_in_unresolved_type(&noir_type_alias.typ) + self.find_in_unresolved_type(&noir_type_alias.typ); } fn find_in_noir_struct(&mut self, noir_struct: &NoirStruct) { @@ -321,7 +321,7 @@ impl<'a> NodeFinder<'a> { for (name, _) in parameters { self.local_variables.insert(name.to_string(), name.span()); } - self.find_in_block_expression(body) + self.find_in_block_expression(body); }; self.type_parameters = old_type_parameters; @@ -330,7 +330,7 @@ impl<'a> NodeFinder<'a> { self.find_in_unresolved_type(typ); if let Some(default_value) = default_value { - self.find_in_expression(default_value) + self.find_in_expression(default_value); } } TraitItem::Type { name: _ } => (), @@ -855,19 +855,18 @@ impl<'a> NodeFinder<'a> { function_completion_kind, requested_items, ); - } else { - if let Some(module_id) = self.resolve_module(segments) { - let at_root = false; - self.complete_in_module( - module_id, - &prefix, - path_kind, - at_root, - module_completion_kind, - function_completion_kind, - requested_items, - ); - }; + } + if let Some(module_id) = self.resolve_module(segments) { + let at_root = false; + self.complete_in_module( + module_id, + &prefix, + path_kind, + at_root, + module_completion_kind, + function_completion_kind, + requested_items, + ); } } } From 959495452c2eef1a1811ea3950405ff504382ce6 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 08:42:01 -0300 Subject: [PATCH 32/37] Fix if-else --- tooling/lsp/src/requests/completion.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index b83a4b243e4..7818fed9793 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -855,8 +855,7 @@ impl<'a> NodeFinder<'a> { function_completion_kind, requested_items, ); - } - if let Some(module_id) = self.resolve_module(segments) { + } else if let Some(module_id) = self.resolve_module(segments) { let at_root = false; self.complete_in_module( module_id, From a33d7c2a3b9785929a77afbe5dcdd385f1d16ea3 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 10:38:31 -0300 Subject: [PATCH 33/37] Suggest assert and assert_eq overloads --- tooling/lsp/src/requests/completion.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 7818fed9793..a52db387395 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -1157,6 +1157,12 @@ impl<'a> NodeFinder<'a> { "assert(${1:predicate})", Some("fn(T)".to_string()), )); + self.completion_items.push(snippet_completion_item( + "assert(…)", + CompletionItemKind::FUNCTION, + "assert(${1:predicate}, ${2:message})", + Some("fn(T, str)".to_string()), + )); } if name_matches("assert_eq", prefix) { @@ -1166,6 +1172,12 @@ impl<'a> NodeFinder<'a> { "assert_eq(${1:lhs}, ${2:rhs})", Some("fn(T, T)".to_string()), )); + self.completion_items.push(snippet_completion_item( + "assert_eq(…)", + CompletionItemKind::FUNCTION, + "assert_eq(${1:lhs}, ${2:rhs}, ${3:message})", + Some("fn(T, T, str)".to_string()), + )); } for keyword in ["false", "true"] { @@ -1705,6 +1717,12 @@ mod completion_tests { "assert(${1:predicate})", Some("fn(T)".to_string()), ), + snippet_completion_item( + "assert(…)", + CompletionItemKind::FUNCTION, + "assert(${1:predicate}, ${2:message})", + Some("fn(T, str)".to_string()), + ), snippet_completion_item( "assert_constant(…)", CompletionItemKind::FUNCTION, @@ -1717,6 +1735,12 @@ mod completion_tests { "assert_eq(${1:lhs}, ${2:rhs})", Some("fn(T, T)".to_string()), ), + snippet_completion_item( + "assert_eq(…)", + CompletionItemKind::FUNCTION, + "assert_eq(${1:lhs}, ${2:rhs}, ${3:message})", + Some("fn(T, T, str)".to_string()), + ), ], ) .await; From 65efca8eee2ffae0f0fd5dbed0f8851b91e8cf14 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 14:39:39 -0300 Subject: [PATCH 34/37] Dehardcode a bit the list of built-in types --- Cargo.lock | 1 + compiler/noirc_frontend/Cargo.toml | 4 +- compiler/noirc_frontend/src/lexer/token.rs | 62 +++++++++++++++++++++- tooling/lsp/Cargo.toml | 1 + tooling/lsp/src/requests/completion.rs | 35 ++++++------ 5 files changed, 79 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47b63ff2f4f..cbcbfb5bfa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2737,6 +2737,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "strum", "thiserror", "tokio", "tower", diff --git a/compiler/noirc_frontend/Cargo.toml b/compiler/noirc_frontend/Cargo.toml index f7439a09204..7ef8870eaa8 100644 --- a/compiler/noirc_frontend/Cargo.toml +++ b/compiler/noirc_frontend/Cargo.toml @@ -32,12 +32,12 @@ tracing.workspace = true petgraph = "0.6" rangemap = "1.4.0" lalrpop-util = { version = "0.20.2", features = ["lexer"] } +strum = "0.24" +strum_macros = "0.24" [dev-dependencies] base64.workspace = true -strum = "0.24" -strum_macros = "0.24" [build-dependencies] lalrpop = "0.20.2" diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 2284991bbc0..d0b0d633c70 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -530,6 +530,11 @@ impl IntType { Ok(Some(Token::IntType(IntType::Unsigned(str_as_u32)))) } } + + // Returns all the names of built-in integer types + pub fn builtin_types() -> [&'static str; 8] { + ["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"] + } } /// TestScope is used to specify additional annotations for test functions @@ -883,8 +888,7 @@ impl AsRef for SecondaryAttribute { /// Note that `self` is not present - it is a contextual keyword rather than a true one as it is /// only special within `impl`s. Otherwise `self` functions as a normal identifier. -#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone, PartialOrd, Ord)] -#[cfg_attr(test, derive(strum_macros::EnumIter))] +#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone, PartialOrd, Ord, strum_macros::EnumIter)] pub enum Keyword { As, Assert, @@ -1048,6 +1052,60 @@ impl Keyword { Some(Token::Keyword(keyword)) } + + /// If this keyword corresponds to a built-in type, returns that type's name. + pub fn builtin_type(&self) -> Option<&'static str> { + match self { + Keyword::Bool => Some("bool"), + Keyword::Expr => Some("Expr"), + Keyword::Field => Some("Field"), + Keyword::FunctionDefinition => Some("FunctionDefinition"), + Keyword::StructDefinition => Some("StructDefinition"), + Keyword::TraitConstraint => Some("TraitConstraint"), + Keyword::TraitDefinition => Some("TraitDefinition"), + Keyword::TypeType => Some("Type"), + + Keyword::As + | Keyword::Assert + | Keyword::AssertEq + | Keyword::Break + | Keyword::CallData + | Keyword::Char + | Keyword::Comptime + | Keyword::Constrain + | Keyword::Continue + | Keyword::Contract + | Keyword::Crate + | Keyword::Dep + | Keyword::Else + | Keyword::Fn + | Keyword::For + | Keyword::FormatString + | Keyword::Global + | Keyword::If + | Keyword::Impl + | Keyword::In + | Keyword::Let + | Keyword::Mod + | Keyword::Module + | Keyword::Mut + | Keyword::Pub + | Keyword::Quoted + | Keyword::Return + | Keyword::ReturnData + | Keyword::String + | Keyword::Struct + | Keyword::Super + | Keyword::TopLevelItem + | Keyword::Trait + | Keyword::Type + | Keyword::Unchecked + | Keyword::Unconstrained + | Keyword::Use + | Keyword::Where + | Keyword::While => None, + } + } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/tooling/lsp/Cargo.toml b/tooling/lsp/Cargo.toml index ac3e3b1d30a..03c6c9105ba 100644 --- a/tooling/lsp/Cargo.toml +++ b/tooling/lsp/Cargo.toml @@ -22,6 +22,7 @@ noirc_frontend.workspace = true noirc_artifacts.workspace = true serde.workspace = true serde_json.workspace = true +strum = "0.24" tower.workspace = true async-lsp = { workspace = true, features = ["omni-trait"] } serde_with = "3.2.0" diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index a52db387395..55b97dc5f4d 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -29,8 +29,10 @@ use noirc_frontend::{ macros_api::{ModuleDefId, NodeInterner, StructId}, node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, parser::{Item, ItemKind}, + token::{IntType, Keyword}, ParsedModule, Type, }; +use strum::IntoEnumIterator; use crate::{utils, LspState}; @@ -1192,26 +1194,19 @@ impl<'a> NodeFinder<'a> { } fn builtin_types_completion(&mut self, prefix: &str) { - for typ in [ - "bool", - "i8", - "i16", - "i32", - "i64", - "u8", - "u16", - "u32", - "u64", - "str", - "Expr", - "Field", - "FunctionDefinition", - "Quoted", - "StructDefinition", - "TraitConstraint", - "TraitDefinition", - "Type", - ] { + for keyword in Keyword::iter() { + if let Some(typ) = keyword.builtin_type() { + if name_matches(typ, prefix) { + self.completion_items.push(simple_completion_item( + typ, + CompletionItemKind::STRUCT, + Some(typ.to_string()), + )); + } + } + } + + for typ in IntType::builtin_types() { if name_matches(typ, prefix) { self.completion_items.push(simple_completion_item( typ, From 6034099059622c7d85a26e81345fe3116d7898ec Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 14:48:36 -0300 Subject: [PATCH 35/37] Move functions to lsp --- compiler/noirc_frontend/src/lexer/token.rs | 59 -------------------- tooling/lsp/src/requests/completion.rs | 64 +++++++++++++++++++++- 2 files changed, 61 insertions(+), 62 deletions(-) diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index d0b0d633c70..4222d2b585f 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -530,11 +530,6 @@ impl IntType { Ok(Some(Token::IntType(IntType::Unsigned(str_as_u32)))) } } - - // Returns all the names of built-in integer types - pub fn builtin_types() -> [&'static str; 8] { - ["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"] - } } /// TestScope is used to specify additional annotations for test functions @@ -1052,60 +1047,6 @@ impl Keyword { Some(Token::Keyword(keyword)) } - - /// If this keyword corresponds to a built-in type, returns that type's name. - pub fn builtin_type(&self) -> Option<&'static str> { - match self { - Keyword::Bool => Some("bool"), - Keyword::Expr => Some("Expr"), - Keyword::Field => Some("Field"), - Keyword::FunctionDefinition => Some("FunctionDefinition"), - Keyword::StructDefinition => Some("StructDefinition"), - Keyword::TraitConstraint => Some("TraitConstraint"), - Keyword::TraitDefinition => Some("TraitDefinition"), - Keyword::TypeType => Some("Type"), - - Keyword::As - | Keyword::Assert - | Keyword::AssertEq - | Keyword::Break - | Keyword::CallData - | Keyword::Char - | Keyword::Comptime - | Keyword::Constrain - | Keyword::Continue - | Keyword::Contract - | Keyword::Crate - | Keyword::Dep - | Keyword::Else - | Keyword::Fn - | Keyword::For - | Keyword::FormatString - | Keyword::Global - | Keyword::If - | Keyword::Impl - | Keyword::In - | Keyword::Let - | Keyword::Mod - | Keyword::Module - | Keyword::Mut - | Keyword::Pub - | Keyword::Quoted - | Keyword::Return - | Keyword::ReturnData - | Keyword::String - | Keyword::Struct - | Keyword::Super - | Keyword::TopLevelItem - | Keyword::Trait - | Keyword::Type - | Keyword::Unchecked - | Keyword::Unconstrained - | Keyword::Use - | Keyword::Where - | Keyword::While => None, - } - } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 55b97dc5f4d..17cff0e5eb0 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -29,7 +29,7 @@ use noirc_frontend::{ macros_api::{ModuleDefId, NodeInterner, StructId}, node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, parser::{Item, ItemKind}, - token::{IntType, Keyword}, + token::Keyword, ParsedModule, Type, }; use strum::IntoEnumIterator; @@ -1195,7 +1195,7 @@ impl<'a> NodeFinder<'a> { fn builtin_types_completion(&mut self, prefix: &str) { for keyword in Keyword::iter() { - if let Some(typ) = keyword.builtin_type() { + if let Some(typ) = keyword_builtin_type(&keyword) { if name_matches(typ, prefix) { self.completion_items.push(simple_completion_item( typ, @@ -1206,7 +1206,7 @@ impl<'a> NodeFinder<'a> { } } - for typ in IntType::builtin_types() { + for typ in builtin_integer_types() { if name_matches(typ, prefix) { self.completion_items.push(simple_completion_item( typ, @@ -1226,6 +1226,64 @@ fn name_matches(name: &str, prefix: &str) -> bool { name.starts_with(prefix) } +fn builtin_integer_types() -> [&'static str; 8] { + ["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"] +} + +/// If this keyword corresponds to a built-in type, returns that type's name. +fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { + match keyword { + Keyword::Bool => Some("bool"), + Keyword::Expr => Some("Expr"), + Keyword::Field => Some("Field"), + Keyword::FunctionDefinition => Some("FunctionDefinition"), + Keyword::StructDefinition => Some("StructDefinition"), + Keyword::TraitConstraint => Some("TraitConstraint"), + Keyword::TraitDefinition => Some("TraitDefinition"), + Keyword::TypeType => Some("Type"), + + Keyword::As + | Keyword::Assert + | Keyword::AssertEq + | Keyword::Break + | Keyword::CallData + | Keyword::Char + | Keyword::Comptime + | Keyword::Constrain + | Keyword::Continue + | Keyword::Contract + | Keyword::Crate + | Keyword::Dep + | Keyword::Else + | Keyword::Fn + | Keyword::For + | Keyword::FormatString + | Keyword::Global + | Keyword::If + | Keyword::Impl + | Keyword::In + | Keyword::Let + | Keyword::Mod + | Keyword::Module + | Keyword::Mut + | Keyword::Pub + | Keyword::Quoted + | Keyword::Return + | Keyword::ReturnData + | Keyword::String + | Keyword::Struct + | Keyword::Super + | Keyword::TopLevelItem + | Keyword::Trait + | Keyword::Type + | Keyword::Unchecked + | Keyword::Unconstrained + | Keyword::Use + | Keyword::Where + | Keyword::While => None, + } +} + fn module_completion_item(name: impl Into) -> CompletionItem { simple_completion_item(name, CompletionItemKind::MODULE, None) } From 00acd4a03a07b7eceb4c0c50a5ba83d9abedad66 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 14:49:14 -0300 Subject: [PATCH 36/37] Extract `builtin_values_completion` --- tooling/lsp/src/requests/completion.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 17cff0e5eb0..b45a52f527b 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -733,6 +733,7 @@ impl<'a> NodeFinder<'a> { RequestedItems::AnyItems => { self.local_variables_completion(&prefix); self.builtin_functions_completion(&prefix); + self.builtin_values_completion(&prefix); } RequestedItems::OnlyTypes => { self.builtin_types_completion(&prefix); @@ -1181,7 +1182,9 @@ impl<'a> NodeFinder<'a> { Some("fn(T, T, str)".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( From c4119b46f46acef63a3c178000bee87920ceaae3 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 12 Aug 2024 15:01:29 -0300 Subject: [PATCH 37/37] Dehardcode built-in functions --- tooling/lsp/src/requests/completion.rs | 112 ++------------- .../lsp/src/requests/completion/builtins.rs | 127 ++++++++++++++++++ 2 files changed, 141 insertions(+), 98 deletions(-) create mode 100644 tooling/lsp/src/requests/completion/builtins.rs diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index b45a52f527b..48616c0f52d 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -4,6 +4,7 @@ use std::{ }; use async_lsp::ResponseError; +use builtins::{builtin_integer_types, keyword_builtin_function, keyword_builtin_type}; use fm::{FileId, PathString}; use lsp_types::{ CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionParams, @@ -38,6 +39,8 @@ 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 { @@ -1153,34 +1156,17 @@ impl<'a> NodeFinder<'a> { } fn builtin_functions_completion(&mut self, prefix: &str) { - if name_matches("assert", prefix) { - self.completion_items.push(snippet_completion_item( - "assert(…)", - CompletionItemKind::FUNCTION, - "assert(${1:predicate})", - Some("fn(T)".to_string()), - )); - self.completion_items.push(snippet_completion_item( - "assert(…)", - CompletionItemKind::FUNCTION, - "assert(${1:predicate}, ${2:message})", - Some("fn(T, str)".to_string()), - )); - } - - if name_matches("assert_eq", prefix) { - self.completion_items.push(snippet_completion_item( - "assert_eq(…)", - CompletionItemKind::FUNCTION, - "assert_eq(${1:lhs}, ${2:rhs})", - Some("fn(T, T)".to_string()), - )); - self.completion_items.push(snippet_completion_item( - "assert_eq(…)", - CompletionItemKind::FUNCTION, - "assert_eq(${1:lhs}, ${2:rhs}, ${3:message})", - Some("fn(T, T, str)".to_string()), - )); + 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()), + )); + } + } } } @@ -1229,64 +1215,6 @@ fn name_matches(name: &str, prefix: &str) -> bool { name.starts_with(prefix) } -fn builtin_integer_types() -> [&'static str; 8] { - ["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"] -} - -/// If this keyword corresponds to a built-in type, returns that type's name. -fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { - match keyword { - Keyword::Bool => Some("bool"), - Keyword::Expr => Some("Expr"), - Keyword::Field => Some("Field"), - Keyword::FunctionDefinition => Some("FunctionDefinition"), - Keyword::StructDefinition => Some("StructDefinition"), - Keyword::TraitConstraint => Some("TraitConstraint"), - Keyword::TraitDefinition => Some("TraitDefinition"), - Keyword::TypeType => Some("Type"), - - Keyword::As - | Keyword::Assert - | Keyword::AssertEq - | Keyword::Break - | Keyword::CallData - | Keyword::Char - | Keyword::Comptime - | Keyword::Constrain - | Keyword::Continue - | Keyword::Contract - | Keyword::Crate - | Keyword::Dep - | Keyword::Else - | Keyword::Fn - | Keyword::For - | Keyword::FormatString - | Keyword::Global - | Keyword::If - | Keyword::Impl - | Keyword::In - | Keyword::Let - | Keyword::Mod - | Keyword::Module - | Keyword::Mut - | Keyword::Pub - | Keyword::Quoted - | Keyword::Return - | Keyword::ReturnData - | Keyword::String - | Keyword::Struct - | Keyword::Super - | Keyword::TopLevelItem - | Keyword::Trait - | Keyword::Type - | Keyword::Unchecked - | Keyword::Unconstrained - | Keyword::Use - | Keyword::Where - | Keyword::While => None, - } -} - fn module_completion_item(name: impl Into) -> CompletionItem { simple_completion_item(name, CompletionItemKind::MODULE, None) } @@ -1773,12 +1701,6 @@ mod completion_tests { "assert(${1:predicate})", Some("fn(T)".to_string()), ), - snippet_completion_item( - "assert(…)", - CompletionItemKind::FUNCTION, - "assert(${1:predicate}, ${2:message})", - Some("fn(T, str)".to_string()), - ), snippet_completion_item( "assert_constant(…)", CompletionItemKind::FUNCTION, @@ -1791,12 +1713,6 @@ mod completion_tests { "assert_eq(${1:lhs}, ${2:rhs})", Some("fn(T, T)".to_string()), ), - snippet_completion_item( - "assert_eq(…)", - CompletionItemKind::FUNCTION, - "assert_eq(${1:lhs}, ${2:rhs}, ${3:message})", - Some("fn(T, T, str)".to_string()), - ), ], ) .await; diff --git a/tooling/lsp/src/requests/completion/builtins.rs b/tooling/lsp/src/requests/completion/builtins.rs new file mode 100644 index 00000000000..070be109f13 --- /dev/null +++ b/tooling/lsp/src/requests/completion/builtins.rs @@ -0,0 +1,127 @@ +use noirc_frontend::token::Keyword; + +pub(super) fn builtin_integer_types() -> [&'static str; 8] { + ["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"] +} + +/// If a keyword corresponds to a built-in type, returns that type's name. +pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { + match keyword { + Keyword::Bool => Some("bool"), + Keyword::Expr => Some("Expr"), + Keyword::Field => Some("Field"), + Keyword::FunctionDefinition => Some("FunctionDefinition"), + Keyword::StructDefinition => Some("StructDefinition"), + Keyword::TraitConstraint => Some("TraitConstraint"), + Keyword::TraitDefinition => Some("TraitDefinition"), + Keyword::TypeType => Some("Type"), + + Keyword::As + | Keyword::Assert + | Keyword::AssertEq + | Keyword::Break + | Keyword::CallData + | Keyword::Char + | Keyword::Comptime + | Keyword::Constrain + | Keyword::Continue + | Keyword::Contract + | Keyword::Crate + | Keyword::Dep + | Keyword::Else + | Keyword::Fn + | Keyword::For + | Keyword::FormatString + | Keyword::Global + | Keyword::If + | Keyword::Impl + | Keyword::In + | Keyword::Let + | Keyword::Mod + | Keyword::Module + | Keyword::Mut + | Keyword::Pub + | Keyword::Quoted + | Keyword::Return + | Keyword::ReturnData + | Keyword::String + | Keyword::Struct + | Keyword::Super + | Keyword::TopLevelItem + | Keyword::Trait + | Keyword::Type + | Keyword::Unchecked + | Keyword::Unconstrained + | Keyword::Use + | Keyword::Where + | Keyword::While => None, + } +} + +pub(super) struct BuiltInFunction { + pub(super) name: &'static str, + pub(super) parameters: &'static str, + pub(super) description: &'static str, +} + +/// If a keyword corresponds to a built-in function, returns info about it +pub(super) fn keyword_builtin_function(keyword: &Keyword) -> Option { + match keyword { + Keyword::Assert => Some(BuiltInFunction { + name: "assert", + parameters: "${1:predicate}", + description: "fn(T)", + }), + Keyword::AssertEq => Some(BuiltInFunction { + name: "assert_eq", + parameters: "${1:lhs}, ${2:rhs}", + description: "fn(T, T)", + }), + + Keyword::As + | Keyword::Bool + | Keyword::Break + | Keyword::CallData + | Keyword::Char + | Keyword::Comptime + | Keyword::Constrain + | Keyword::Continue + | Keyword::Contract + | Keyword::Crate + | Keyword::Dep + | Keyword::Else + | Keyword::Expr + | Keyword::Field + | Keyword::Fn + | Keyword::For + | Keyword::FormatString + | Keyword::FunctionDefinition + | Keyword::Global + | Keyword::If + | Keyword::Impl + | Keyword::In + | Keyword::Let + | Keyword::Mod + | Keyword::Module + | Keyword::Mut + | Keyword::Pub + | Keyword::Quoted + | Keyword::Return + | Keyword::ReturnData + | Keyword::String + | Keyword::Struct + | Keyword::StructDefinition + | Keyword::Super + | Keyword::TopLevelItem + | Keyword::Trait + | Keyword::TraitConstraint + | Keyword::TraitDefinition + | Keyword::Type + | Keyword::TypeType + | Keyword::Unchecked + | Keyword::Unconstrained + | Keyword::Use + | Keyword::Where + | Keyword::While => None, + } +}