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/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/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 2284991bbc0..4222d2b585f 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -883,8 +883,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, 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 0c1f7e724dc..48616c0f52d 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -1,32 +1,78 @@ use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap, HashSet}, future::{self, Future}, }; 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, - CompletionResponse, + CompletionResponse, InsertTextFormat, }; -use noirc_errors::Span; +use noirc_errors::{Location, Span}; use noirc_frontend::{ - ast::{Ident, Path, PathKind, PathSegment, UseTree, UseTreeKind}, + ast::{ + ArrayLiteral, AsTraitPath, BlockExpression, CallExpression, CastExpression, + 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, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedType, UseTree, UseTreeKind, + }, graph::{CrateId, Dependency}, hir::{ 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, TraitId, TypeAliasId}, + node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, parser::{Item, ItemKind}, + token::Keyword, ParsedModule, Type, }; +use strum::IntoEnumIterator; use crate::{utils, LspState}; use super::process_request; +mod builtins; + +/// When finding items in a module, whether to show only direct children or all visible items. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum ModuleCompletionKind { + // Only show a module's direct children. This is used when completing a use statement + // or a path after the first segment. + DirectChildren, + // Show all of a module's visible items. This is used when completing a path outside + // of a use statement (in regular code) when the path is just a single segment: + // we want to find items exposed in the current module. + AllVisibleItems, +} + +/// When suggest a function as a result of completion, whether to autocomplete its name or its name and parameters. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum 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, +} + pub(crate) fn on_completion_request( state: &mut LspState, params: CompletionParams, @@ -64,13 +110,25 @@ pub(crate) fn on_completion_request( } 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, + /// 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, + /// Type parameters in the current scope. These are collected when entering + /// a struct, a function, etc., and cleared afterwards. + type_parameters: HashSet, } impl<'a> NodeFinder<'a> { @@ -94,98 +152,660 @@ 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 } + Self { + file, + byte_index, + byte, + root_module_id, + module_id, + 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 { 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 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; + } + 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::Impl(type_impl) => self.find_in_type_impl(type_impl), + ItemKind::Global(let_statement) => self.find_in_let_statement(let_statement, false), + 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(_) => (), + } + } + + 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); + + for param in &noir_function.def.parameters { + self.find_in_unresolved_type(¶m.typ); + } + + 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); + } + + self.find_in_block_expression(&noir_function.def.body); + + self.type_parameters = old_type_parameters; + } + + 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); + + for item in &noir_trait_impl.items { + self.find_in_trait_impl_item(item); + } + + self.type_parameters.clear(); + } + + 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(_, _, _) => (), + TraitImplItem::Type { .. } => (), + } + } + + fn find_in_type_impl(&mut self, type_impl: &TypeImpl) { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&type_impl.generics); + + 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(); + } + + 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) { + self.type_parameters.clear(); + self.collect_type_parameters_in_generics(&noir_struct.generics); + + for (_name, unresolved_type) in &noir_struct.fields { + self.find_in_unresolved_type(unresolved_type); + } - if let Some(completion) = completion { - return Some(completion); + self.type_parameters.clear(); + } + + fn find_in_noir_trait(&mut self, noir_trait: &NoirTrait) { + for item in &noir_trait.items { + self.find_in_trait_item(item); + } + } + + fn find_in_trait_item(&mut self, trait_item: &TraitItem) { + match trait_item { + TraitItem::Function { + name: _, + 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 { + self.find_in_unresolved_type(unresolved_type); + } + + self.find_in_function_return_type(return_type); + + for unresolved_trait_constraint in where_clause { + self.find_in_unresolved_type(&unresolved_trait_constraint.typ); } + + 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); + }; + + self.type_parameters = old_type_parameters; + } + TraitItem::Constant { name: _, typ, default_value } => { + self.find_in_unresolved_type(typ); + + if let Some(default_value) = default_value { + self.find_in_expression(default_value); + } + } + TraitItem::Type { name: _ } => (), + } + } + + fn find_in_block_expression(&mut self, block_expression: &BlockExpression) { + 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; + } + + 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); + } + 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(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); + } + 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(); - None + self.find_in_statement(statement); + + self.local_variables = old_local_variables; + } + 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 => (), + } } - fn find_in_use_tree( - &self, - use_tree: &UseTree, - prefixes: &mut Vec, - ) -> Option { + fn find_in_let_statement( + &mut self, + let_statement: &LetStatement, + collect_local_variables: bool, + ) { + 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); + } + } + + 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); + } + } + + fn find_in_assign_statement( + &mut self, + assign_statement: &noirc_frontend::ast::AssignStatement, + ) { + 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) { + let old_local_variables = self.local_variables.clone(); + let ident = &for_loop_statement.identifier; + self.local_variables.insert(ident.to_string(), ident.span()); + + self.find_in_for_range(&for_loop_statement.range); + self.find_in_expression(&for_loop_statement.block); + + self.local_variables = old_local_variables; + } + + fn find_in_lvalue(&mut self, lvalue: &LValue) { + match lvalue { + LValue::Ident(_) => (), + LValue::MemberAccess { object, field_name: _, span: _ } => self.find_in_lvalue(object), + LValue::Index { array, index, span: _ } => { + 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) { + match for_range { + ForRange::Range(start, 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]) { + for expression in expressions { + self.find_in_expression(expression); + } + } + + 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) => { + self.find_in_block_expression(block_expression); + } + noirc_frontend::ast::ExpressionKind::Prefix(prefix_expression) => { + self.find_in_expression(&prefix_expression.rhs); + } + noirc_frontend::ast::ExpressionKind::Index(index_expression) => { + self.find_in_index_expression(index_expression); + } + noirc_frontend::ast::ExpressionKind::Call(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); + } + noirc_frontend::ast::ExpressionKind::Constructor(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); + } + noirc_frontend::ast::ExpressionKind::Cast(cast_expression) => { + self.find_in_cast_expression(cast_expression); + } + noirc_frontend::ast::ExpressionKind::Infix(infix_expression) => { + self.find_in_infix_expression(infix_expression); + } + 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, RequestedItems::AnyItems); + } + noirc_frontend::ast::ExpressionKind::Tuple(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); + } + noirc_frontend::ast::ExpressionKind::Unquote(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 + let old_local_variables = self.local_variables.clone(); + self.local_variables.clear(); + + self.find_in_block_expression(block_expression); + + self.local_variables = old_local_variables; + } + 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 => (), + } + } + + 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), + Literal::Bool(_) + | Literal::Integer(_, _) + | Literal::Str(_) + | Literal::RawStr(_, _) + | Literal::FmtStr(_) + | Literal::Unit => (), + } + } + + 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 } => { + self.find_in_expression(repeated_element); + self.find_in_expression(length); + } + } + } + + 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) { + 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) { + 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) { + self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); + + for (_field_name, expression) in &constructor_expression.fields { + self.find_in_expression(expression); + } + } + + fn find_in_member_access_expression( + &mut self, + member_access_expression: &MemberAccessExpression, + ) { + 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); + } + + 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) { + self.find_in_expression(&if_expression.condition); + + let old_local_variables = self.local_variables.clone(); + self.find_in_expression(&if_expression.consequence); + self.local_variables = old_local_variables; + + 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) { + for (_, unresolved_type) in &lambda.parameters { + self.find_in_unresolved_type(unresolved_type); + } + + let old_local_variables = self.local_variables.clone(); + for (pattern, _) in &lambda.parameters { + self.collect_local_variables(pattern); + } + + self.find_in_expression(&lambda.body); + + self.local_variables = old_local_variables; + } + + 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) { + match return_type { + 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]) { + for unresolved_type in unresolved_type { + self.find_in_unresolved_type(unresolved_type); + } + } + + fn find_in_unresolved_type(&mut self, unresolved_type: &UnresolvedType) { + if let Some(span) = unresolved_type.span { + if !self.includes_span(span) { + return; + } + } + + 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, _) => { + self.find_in_path(path, RequestedItems::OnlyTypes); + 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); + } + 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) => { + 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) => { + 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 => (), + } + } + + 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; + } + + 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 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 { + return; + }; + + let module_completion_kind = if after_colons { + ModuleCompletionKind::DirectChildren + } else { + ModuleCompletionKind::AllVisibleItems + }; + let function_completion_kind = FunctionCompleteKind::NameAndParameters; + + self.complete_in_module( + module_id, + &prefix, + path.kind, + at_root, + module_completion_kind, + function_completion_kind, + requested_items, + ); + + if is_single_segment { + match requested_items { + RequestedItems::AnyItems => { + self.local_variables_completion(&prefix); + self.builtin_functions_completion(&prefix); + self.builtin_values_completion(&prefix); + } + RequestedItems::OnlyTypes => { + self.builtin_types_completion(&prefix); + self.type_parameters_completion(&prefix); + } + } + } + } + + 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); + 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 + }; + + self.completion_items.push(simple_completion_item( + name, + CompletionItemKind::VARIABLE, + description, + )); + } + } + } + + fn type_parameters_completion(&mut self, prefix: &str) { + for name in &self.type_parameters { + if name_matches(name, prefix) { + self.completion_items.push(simple_completion_item( + name, + CompletionItemKind::TYPE_PARAMETER, + None, + )); + } + } + } + + 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()); - 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()); 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':'); @@ -194,7 +814,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; @@ -206,68 +826,161 @@ impl<'a> NodeFinder<'a> { } } + let module_completion_kind = ModuleCompletionKind::DirectChildren; + let function_completion_kind = FunctionCompleteKind::Name; + let requested_items = RequestedItems::AnyItems; + if after_colons { // 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(module_id, prefix, path_kind, at_root) - }) + self.complete_in_module( + module_id, + &prefix, + path_kind, + at_root, + module_completion_kind, + function_completion_kind, + requested_items, + ); + }; } 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) - } else { + self.complete_in_module( + self.module_id, + &prefix, + path_kind, + at_root, + module_completion_kind, + function_completion_kind, + requested_items, + ); + } else if let Some(module_id) = self.resolve_module(segments) { 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, + function_completion_kind, + requested_items, + ); + } + } + } + + fn collect_local_variables(&mut self, pattern: &Pattern) { + match pattern { + Pattern::Identifier(ident) => { + self.local_variables.insert(ident.to_string(), ident.span()); + } + 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); + } } } } + 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(..) => (), + }; + } + + #[allow(clippy::too_many_arguments)] fn complete_in_module( - &self, + &mut self, module_id: ModuleId, - prefix: String, + prefix: &str, path_kind: PathKind, at_root: bool, - ) -> Option { + module_completion_kind: ModuleCompletionKind, + function_completion_kind: FunctionCompleteKind, + requested_items: RequestedItems, + ) { 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(), + }; - for ident in module_data.definitions().names() { + 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(module_def_id, name.clone())); + if let Some(completion_item) = self.module_def_id_completion_item( + module_def_id, + name.clone(), + function_completion_kind, + requested_items, + ) { + self.completion_items.push(completion_item); + } } if let Some((module_def_id, _, _)) = per_ns.values { - completion_items - .push(self.module_def_id_completion_item(module_def_id, name.clone())); + if let Some(completion_item) = self.module_def_id_completion_item( + module_def_id, + name.clone(), + function_completion_kind, + requested_items, + ) { + self.completion_items.push(completion_item); + } } } } @@ -275,57 +988,118 @@ 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) { - completion_items.push(crate_completion_item(dependency_name)); + if name_matches(&dependency_name, prefix) { + self.completion_items.push(crate_completion_item(dependency_name)); } } - if name_matches("crate::", &prefix) { - completion_items.push(simple_completion_item( + if name_matches("crate::", prefix) { + self.completion_items.push(simple_completion_item( "crate::", CompletionItemKind::KEYWORD, None, )); } - if module_data.parent.is_some() && name_matches("super::", &prefix) { - completion_items.push(simple_completion_item( + if module_data.parent.is_some() && name_matches("super::", prefix) { + self.completion_items.push(simple_completion_item( "super::", CompletionItemKind::KEYWORD, None, )); } } - - Some(CompletionResponse::Array(completion_items)) } fn module_def_id_completion_item( &self, module_def_id: ModuleDefId, name: String, - ) -> CompletionItem { - match module_def_id { + function_completion_kind: FunctionCompleteKind, + requested_items: RequestedItems, + ) -> Option { + match requested_items { + RequestedItems::OnlyTypes => match module_def_id { + ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return None, + ModuleDefId::ModuleId(_) + | ModuleDefId::TypeId(_) + | ModuleDefId::TypeAliasId(_) + | ModuleDefId::TraitId(_) => (), + }, + RequestedItems::AnyItems => (), + } + + let completion_item = 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) } 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(&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; + + 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 => { + simple_completion_item(name, CompletionItemKind::FUNCTION, Some(description)) + } + FunctionCompleteKind::NameAndParameters => { + let label = format!("{}(…)", name); + let kind = CompletionItemKind::FUNCTION; + let insert_text = self.compute_function_insert_text(func_meta, &name); + + snippet_completion_item(label, kind, insert_text, Some(description)) + } + } + } - simple_completion_item(name, CompletionItemKind::FUNCTION, Some(description)) + fn compute_function_insert_text(&self, func_meta: &FuncMeta, name: &str) -> 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 { @@ -381,6 +1155,57 @@ impl<'a> NodeFinder<'a> { } } + fn builtin_functions_completion(&mut self, prefix: &str) { + for keyword in Keyword::iter() { + if let Some(func) = keyword_builtin_function(&keyword) { + if name_matches(func.name, prefix) { + self.completion_items.push(snippet_completion_item( + format!("{}(…)", func.name), + CompletionItemKind::FUNCTION, + format!("{}({})", func.name, func.parameters), + Some(func.description.to_string()), + )); + } + } + } + } + + fn builtin_values_completion(&mut self, prefix: &str) { + for keyword in ["false", "true"] { + if name_matches(keyword, prefix) { + self.completion_items.push(simple_completion_item( + keyword, + CompletionItemKind::KEYWORD, + Some("bool".to_string()), + )); + } + } + } + + fn builtin_types_completion(&mut self, prefix: &str) { + for keyword in Keyword::iter() { + if let Some(typ) = keyword_builtin_type(&keyword) { + if name_matches(typ, prefix) { + self.completion_items.push(simple_completion_item( + typ, + CompletionItemKind::STRUCT, + Some(typ.to_string()), + )); + } + } + } + + for typ in builtin_integer_types() { + if name_matches(typ, prefix) { + self.completion_items.push(simple_completion_item( + typ, + CompletionItemKind::STRUCT, + Some(typ.to_string()), + )); + } + } + } + fn includes_span(&self, span: Span) -> bool { span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize } @@ -425,6 +1250,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, + } +} + #[cfg(test)] mod completion_tests { use crate::{notifications::on_did_open_text_document, test_utils}; @@ -701,8 +1554,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 +1566,639 @@ 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 foobar {} + + fn main() { + fo>|< + } + "#; + assert_completion(src, vec![module_completion_item("foobar")]).await; + } + + #[test] + async fn test_complete_path_after_colons_shows_submodule() { + let src = r#" + mod foo { + mod bar {} + } + + fn main() { + foo::>|< + } + "#; + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_complete_path_after_colons_and_letter_shows_submodule() { + let src = r#" + mod foo { + mod bar {} + } + + fn main() { + foo::b>|< + } + "#; + assert_completion(src, vec![module_completion_item("bar")]).await; + } + + #[test] + async fn test_complete_path_with_local_variable() { + let src = r#" + fn main() { + let local = 1; + l>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_shadowed_local_variable() { + let src = r#" + fn main() { + let local = 1; + let local = true; + l>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("bool".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_function_argument() { + let src = r#" + fn main(local: Field) { + l>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "local", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_function() { + let src = r#" + fn hello(x: i32, y: Field) { } + + fn main() { + h>|< + } + "#; + assert_completion( + src, + vec![snippet_completion_item( + "hello(…)", + CompletionItemKind::FUNCTION, + "hello(${1:x}, ${2:y})", + Some("fn(i32, Field)".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_builtin_functions() { + let src = r#" + fn main() { + a>|< + } + "#; + assert_completion( + src, + vec![ + snippet_completion_item( + "assert(…)", + CompletionItemKind::FUNCTION, + "assert(${1:predicate})", + Some("fn(T)".to_string()), + ), + snippet_completion_item( + "assert_constant(…)", + CompletionItemKind::FUNCTION, + "assert_constant(${1:x})", + Some("fn(T)".to_string()), + ), + snippet_completion_item( + "assert_eq(…)", + CompletionItemKind::FUNCTION, + "assert_eq(${1:lhs}, ${2:rhs})", + Some("fn(T, T)".to_string()), + ), + ], + ) + .await; + } + + #[test] + async fn test_complete_path_in_impl() { + let src = r#" + struct SomeStruct {} + + impl SomeStruct { + fn foo() { + S>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "SomeStruct", + CompletionItemKind::STRUCT, + Some("SomeStruct".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_in_trait_impl() { + let src = r#" + struct SomeStruct {} + trait Trait {} + + impl Trait for SomeStruct { + fn foo() { + S>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "SomeStruct", + CompletionItemKind::STRUCT, + Some("SomeStruct".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_for_argument() { + let src = r#" + fn main() { + for index in 0..10 { + i>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "index", + CompletionItemKind::VARIABLE, + Some("u32".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_complete_path_with_lambda_argument() { + let src = r#" + fn lambda(f: fn(i32)) { } + + fn main() { + lambda(|var| v>|<) + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "var", + CompletionItemKind::VARIABLE, + Some("_".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_struct_field_type() { + let src = r#" + struct Something {} + + fn SomeFunction() {} + + struct Another { + some: So>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_function_parameter() { + let src = r#" + struct Something {} + + fn foo(x: So>|<) {} + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_function_return_type() { + let src = r#" + struct Something {} + + fn foo() -> So>|< {} + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_type_alias() { + let src = r#" + struct Something {} + + type Foo = So>|< + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_trait_function() { + let src = r#" + struct Something {} + + trait Trait { + fn foo(s: So>|<); + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_trait_function_return_type() { + let src = r#" + struct Something {} + + trait Trait { + fn foo() -> So>|<; + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_let_type() { + let src = r#" + struct Something {} + + fn main() { + let x: So>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_type_in_lambda_parameter() { + let src = r#" + struct Something {} + + fn main() { + foo(|s: So>|<| s) + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "Something", + CompletionItemKind::STRUCT, + Some("Something".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_builtin_types() { + let src = r#" + fn foo(x: i>|<) {} + "#; + assert_completion( + src, + vec![ + simple_completion_item("i8", CompletionItemKind::STRUCT, Some("i8".to_string())), + simple_completion_item("i16", CompletionItemKind::STRUCT, Some("i16".to_string())), + simple_completion_item("i32", CompletionItemKind::STRUCT, Some("i32".to_string())), + simple_completion_item("i64", CompletionItemKind::STRUCT, Some("i64".to_string())), + ], + ) + .await; + } + + #[test] + async fn test_suggest_true() { + let src = r#" + fn main() { + let x = t>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "true", + CompletionItemKind::KEYWORD, + Some("bool".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_regarding_if_scope() { + let src = r#" + fn main() { + let good = 1; + if true { + let great = 2; + g>|< + } else { + let greater = 3; + } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + simple_completion_item( + "great", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + ], + ) + .await; + + let src = r#" + fn main() { + let good = 1; + if true { + let great = 2; + } else { + let greater = 3; + g>|< + } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + simple_completion_item( + "greater", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + ], + ) + .await; + + let src = r#" + fn main() { + let good = 1; + if true { + let great = 2; + } else { + let greater = 3; + } + g>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_regarding_block_scope() { + let src = r#" + fn main() { + let good = 1; + { + let great = 2; + g>|< + } + } + "#; + assert_completion( + src, + vec![ + simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + simple_completion_item( + "great", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + ), + ], + ) + .await; + + let src = r#" + fn main() { + let good = 1; + { + let great = 2; + } + g>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item( + "good", + CompletionItemKind::VARIABLE, + Some("Field".to_string()), + )], + ) + .await; + } + + #[test] + async fn test_suggest_struct_type_parameter() { + let src = r#" + struct Foo { + context: C>|< + } + "#; + assert_completion( + src, + vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_impl_type_parameter() { + let src = r#" + struct Foo {} + + impl Foo { + fn foo() { + let x: TypeP>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_trait_impl_type_parameter() { + let src = r#" + struct Foo {} + trait Trait {} + + impl Trait for Foo { + fn foo() { + let x: TypeP>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_trait_function_type_parameter() { + let src = r#" + struct Foo {} + trait Trait { + fn foo() { + let x: TypeP>|< + } + } + "#; + assert_completion( + src, + vec![simple_completion_item("TypeParam", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } + + #[test] + async fn test_suggest_function_type_parameters() { + let src = r#" + fn foo(x: C>|<) {} + "#; + assert_completion( + src, + vec![simple_completion_item("Context", CompletionItemKind::TYPE_PARAMETER, None)], + ) + .await; + } } 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, + } +}