Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: LSP completion now works better in the middle of idents #5795

Merged
merged 11 commits into from
Aug 22, 2024
2 changes: 1 addition & 1 deletion docs/docs/noir/concepts/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ fn main() {

### Associated Types and Constants

Traits also support associated types and constaints which can be thought of as additional generics that are referred to by name.
Traits also support associated types and constraints which can be thought of as additional generics that are referred to by name.

Here's an example of a trait with an associated type `Foo` and a constant `Bar`:

Expand Down
235 changes: 201 additions & 34 deletions tooling/lsp/src/requests/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Completion
use noirc_errors::{Location, Span};
use noirc_frontend::{
ast::{
AsTraitPath, BlockExpression, ConstructorExpression, Expression, ForLoopStatement, Ident,
IfExpression, LValue, Lambda, LetStatement, MemberAccessExpression, NoirFunction,
NoirStruct, NoirTraitImpl, Path, PathKind, PathSegment, Pattern, Statement, TraitItem,
TypeImpl, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UseTree, UseTreeKind,
AsTraitPath, BlockExpression, CallExpression, ConstructorExpression, Expression,
ExpressionKind, ForLoopStatement, Ident, IfExpression, LValue, Lambda, LetStatement,
MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, NoirTraitImpl,
Path, PathKind, PathSegment, Pattern, Statement, StatementKind, TraitItem, TypeImpl,
UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UseTree,
UseTreeKind,
},
graph::{CrateId, Dependency},
hir::{
def_map::{CrateDefMap, LocalModuleId, ModuleId},
resolution::path_resolver::{PathResolver, StandardPathResolver},
},
hir_def::traits::Trait,
macros_api::{ExpressionKind, ModuleDefId, NodeInterner, StatementKind, UnresolvedTypeData},
macros_api::{ModuleDefId, NodeInterner},
node_interner::ReferenceId,
parser::{Item, ItemKind},
ParsedModule, StructType, Type,
Expand Down Expand Up @@ -337,6 +339,71 @@ impl<'a> NodeFinder<'a> {
}
}

pub(super) fn find_in_call_expression(&mut self, call_expression: &CallExpression) {
// Check if it's this case:
//
// foo::b>|<(...)
//
// In this case we want to suggest items in foo but if they are functions
// we don't want to insert arguments, because they are already there (even if
// they could be wrong) just because inserting them would lead to broken code.
if let ExpressionKind::Variable(path) = &call_expression.func.kind {
if self.includes_span(path.span) {
self.find_in_path_impl(path, RequestedItems::AnyItems, true);
return;
}
}

// Check if it's this case:
//
// foo.>|<(...)
//
// "foo." is actually broken, but it's parsed as "foo", so this is seen
// as "foo(...)" but if we are at a dot right after "foo" it means it's
// the above case and we want to suggest methods of foo's type.
let after_dot = self.byte == Some(b'.');
if after_dot && call_expression.func.span.end() as usize == self.byte_index - 1 {
let location = Location::new(call_expression.func.span, self.file);
if let Some(typ) = self.interner.type_at_location(location) {
let typ = typ.follow_bindings();
let prefix = "";
self.complete_type_fields_and_methods(&typ, prefix, FunctionCompletionKind::Name);
return;
}
}

self.find_in_expression(&call_expression.func);
self.find_in_expressions(&call_expression.arguments);
}

pub(super) fn find_in_method_call_expression(
&mut self,
method_call_expression: &MethodCallExpression,
) {
// Check if it's this case:
//
// foo.b>|<(...)
//
// In this case we want to suggest items in foo but if they are functions
// we don't want to insert arguments, because they are already there (even if
// they could be wrong) just because inserting them would lead to broken code.
if self.includes_span(method_call_expression.method_name.span()) {
let location = Location::new(method_call_expression.object.span, self.file);
if let Some(typ) = self.interner.type_at_location(location) {
let typ = typ.follow_bindings();
let prefix = method_call_expression.method_name.to_string();
let offset =
self.byte_index - method_call_expression.method_name.span().start() as usize;
let prefix = prefix[0..offset].to_string();
self.complete_type_fields_and_methods(&typ, &prefix, FunctionCompletionKind::Name);
return;
}
}

self.find_in_expression(&method_call_expression.object);
self.find_in_expressions(&method_call_expression.arguments);
}

fn find_in_block_expression(&mut self, block_expression: &BlockExpression) {
let old_local_variables = self.local_variables.clone();
for statement in &block_expression.statements {
Expand Down Expand Up @@ -417,7 +484,11 @@ impl<'a> NodeFinder<'a> {
{
let typ = self.interner.definition_type(definition_id);
let prefix = "";
self.complete_type_fields_and_methods(&typ, prefix);
self.complete_type_fields_and_methods(
&typ,
prefix,
FunctionCompletionKind::NameAndParameters,
);
}
}
}
Expand Down Expand Up @@ -507,7 +578,11 @@ impl<'a> NodeFinder<'a> {
if let Some(typ) = self.interner.type_at_location(location) {
let typ = typ.follow_bindings();
let prefix = "";
self.complete_type_fields_and_methods(&typ, prefix);
self.complete_type_fields_and_methods(
&typ,
prefix,
FunctionCompletionKind::NameAndParameters,
);
}
}
}
Expand Down Expand Up @@ -569,7 +644,11 @@ impl<'a> NodeFinder<'a> {
if let Some(typ) = self.interner.type_at_location(location) {
let typ = typ.follow_bindings();
let prefix = ident.to_string().to_case(Case::Snake);
self.complete_type_fields_and_methods(&typ, &prefix);
self.complete_type_fields_and_methods(
&typ,
&prefix,
FunctionCompletionKind::NameAndParameters,
);
return;
}
}
Expand Down Expand Up @@ -662,15 +741,61 @@ impl<'a> NodeFinder<'a> {
}

fn find_in_path(&mut self, path: &Path, requested_items: RequestedItems) {
// Only offer completions if we are right at the end of the path
if self.byte_index != path.span.end() as usize {
self.find_in_path_impl(path, requested_items, false);
}

fn find_in_path_impl(
&mut self,
path: &Path,
requested_items: RequestedItems,
mut in_the_middle: bool,
) {
if !self.includes_span(path.span) {
return;
}

let after_colons = self.byte == Some(b':');

let mut idents: Vec<Ident> =
path.segments.iter().map(|segment| segment.ident.clone()).collect();
let mut idents: Vec<Ident> = Vec::new();

// Find in which ident we are in, and in which part of it
// (it could be that we are completting in the middle of an ident)
for segment in &path.segments {
let ident = &segment.ident;

// Check if we are at the end of the ident
if self.byte_index == ident.span().end() as usize {
idents.push(ident.clone());
break;
}

// Check if we are in the middle of an ident
if self.includes_span(ident.span()) {
// If so, take the substring and push that as the list of idents
// we'll do autocompletion for
let offset = self.byte_index - ident.span().start() as usize;
let substring = ident.0.contents[0..offset].to_string();
let ident = Ident::new(
substring,
Span::from(ident.span().start()..ident.span().start() + offset as u32),
);
idents.push(ident);
in_the_middle = true;
break;
}

idents.push(ident.clone());

// Stop if the cursor is right after this ident and '::'
if after_colons && self.byte_index == ident.span().end() as usize + 2 {
break;
}
}

if idents.len() < path.segments.len() {
in_the_middle = true;
}

let prefix;
let at_root;

Expand All @@ -687,6 +812,21 @@ impl<'a> NodeFinder<'a> {
let is_single_segment = !after_colons && idents.is_empty() && path.kind == PathKind::Plain;
let module_id;

let module_completion_kind = if after_colons || !idents.is_empty() {
ModuleCompletionKind::DirectChildren
} else {
ModuleCompletionKind::AllVisibleItems
};

// When completing in the middle of an ident, we don't want to complete
// with function parameters because there might already be function parameters,
// and in the middle of a path it leads to code that won't compile
let function_completion_kind = if in_the_middle {
FunctionCompletionKind::Name
} else {
FunctionCompletionKind::NameAndParameters
};

if idents.is_empty() {
module_id = self.module_id;
} else {
Expand All @@ -702,6 +842,7 @@ impl<'a> NodeFinder<'a> {
&Type::Struct(struct_type, vec![]),
&prefix,
FunctionKind::Any,
function_completion_kind,
);
return;
}
Expand All @@ -712,25 +853,28 @@ impl<'a> NodeFinder<'a> {
ModuleDefId::TypeAliasId(type_alias_id) => {
let type_alias = self.interner.get_type_alias(type_alias_id);
let type_alias = type_alias.borrow();
self.complete_type_methods(&type_alias.typ, &prefix, FunctionKind::Any);
self.complete_type_methods(
&type_alias.typ,
&prefix,
FunctionKind::Any,
function_completion_kind,
);
return;
}
ModuleDefId::TraitId(trait_id) => {
let trait_ = self.interner.get_trait(trait_id);
self.complete_trait_methods(trait_, &prefix, FunctionKind::Any);
self.complete_trait_methods(
trait_,
&prefix,
FunctionKind::Any,
function_completion_kind,
);
return;
}
ModuleDefId::GlobalId(_) => return,
}
}

let module_completion_kind = if after_colons {
ModuleCompletionKind::DirectChildren
} else {
ModuleCompletionKind::AllVisibleItems
};
let function_completion_kind = FunctionCompletionKind::NameAndParameters;

self.complete_in_module(
module_id,
&prefix,
Expand All @@ -745,15 +889,15 @@ impl<'a> NodeFinder<'a> {
match requested_items {
RequestedItems::AnyItems => {
self.local_variables_completion(&prefix);
self.builtin_functions_completion(&prefix);
self.builtin_functions_completion(&prefix, function_completion_kind);
self.builtin_values_completion(&prefix);
}
RequestedItems::OnlyTypes => {
self.builtin_types_completion(&prefix);
self.type_parameters_completion(&prefix);
}
}
self.complete_auto_imports(&prefix, requested_items);
self.complete_auto_imports(&prefix, requested_items, function_completion_kind);
}
}

Expand Down Expand Up @@ -924,17 +1068,30 @@ impl<'a> NodeFinder<'a> {
};
}

fn complete_type_fields_and_methods(&mut self, typ: &Type, prefix: &str) {
fn complete_type_fields_and_methods(
&mut self,
typ: &Type,
prefix: &str,
function_completion_kind: FunctionCompletionKind,
) {
match typ {
Type::Struct(struct_type, generics) => {
self.complete_struct_fields(&struct_type.borrow(), generics, prefix);
}
Type::MutableReference(typ) => {
return self.complete_type_fields_and_methods(typ, prefix);
return self.complete_type_fields_and_methods(
typ,
prefix,
function_completion_kind,
);
}
Type::Alias(type_alias, _) => {
let type_alias = type_alias.borrow();
return self.complete_type_fields_and_methods(&type_alias.typ, prefix);
return self.complete_type_fields_and_methods(
&type_alias.typ,
prefix,
function_completion_kind,
);
}
Type::Tuple(types) => {
self.complete_tuple_fields(types);
Expand All @@ -958,10 +1115,21 @@ impl<'a> NodeFinder<'a> {
| Type::Error => (),
}

self.complete_type_methods(typ, prefix, FunctionKind::SelfType(typ));
self.complete_type_methods(
typ,
prefix,
FunctionKind::SelfType(typ),
function_completion_kind,
);
}

fn complete_type_methods(&mut self, typ: &Type, prefix: &str, function_kind: FunctionKind) {
fn complete_type_methods(
&mut self,
typ: &Type,
prefix: &str,
function_kind: FunctionKind,
function_completion_kind: FunctionCompletionKind,
) {
let Some(methods_by_name) = self.interner.get_type_methods(typ) else {
return;
};
Expand All @@ -971,7 +1139,7 @@ impl<'a> NodeFinder<'a> {
if name_matches(name, prefix) {
if let Some(completion_item) = self.function_completion_item(
func_id,
FunctionCompletionKind::NameAndParameters,
function_completion_kind,
function_kind,
) {
self.completion_items.push(completion_item);
Expand All @@ -987,14 +1155,13 @@ impl<'a> NodeFinder<'a> {
trait_: &Trait,
prefix: &str,
function_kind: FunctionKind,
function_completion_kind: FunctionCompletionKind,
) {
for (name, func_id) in &trait_.method_ids {
if name_matches(name, prefix) {
if let Some(completion_item) = self.function_completion_item(
*func_id,
FunctionCompletionKind::NameAndParameters,
function_kind,
) {
if let Some(completion_item) =
self.function_completion_item(*func_id, function_completion_kind, function_kind)
{
self.completion_items.push(completion_item);
self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id));
}
Expand Down
Loading
Loading