Skip to content

Commit

Permalink
feat: LSP now suggests self fields and methods (#5955)
Browse files Browse the repository at this point in the history
# Description

## Problem

While working with Rust I noticed that if you type a field name or a
method name and there's a `self` variable, those fields and methods are
suggested without you having to explicitly write `self`. I think that's
useful as you sometimes forget about the self prefix, or just because
you end up typing less.

## Summary

This PR implements that.


![lsp-self](https://github.com/user-attachments/assets/976151ab-cdb2-4690-8b62-21514314229a)

## Additional Context



## Documentation

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
asterite authored Sep 6, 2024
1 parent 3dab4dd commit f57ce85
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 14 deletions.
78 changes: 70 additions & 8 deletions tooling/lsp/src/requests/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ struct NodeFinder<'a> {
nesting: usize,
/// The line where an auto_import must be inserted
auto_import_line: usize,
self_type: Option<Type>,
}

impl<'a> NodeFinder<'a> {
Expand Down Expand Up @@ -147,6 +148,7 @@ impl<'a> NodeFinder<'a> {
suggested_module_def_ids: HashSet::new(),
nesting: 0,
auto_import_line: 0,
self_type: None,
}
}

Expand Down Expand Up @@ -191,8 +193,9 @@ impl<'a> NodeFinder<'a> {
fields.remove(&field.0.contents);
}

let self_prefix = false;
for (field, typ) in fields {
self.completion_items.push(struct_field_completion_item(field, typ));
self.completion_items.push(struct_field_completion_item(field, typ, self_prefix));
}
}

Expand Down Expand Up @@ -293,6 +296,7 @@ impl<'a> NodeFinder<'a> {
&prefix,
FunctionKind::Any,
function_completion_kind,
false, // self_prefix
);
return;
}
Expand All @@ -308,6 +312,7 @@ impl<'a> NodeFinder<'a> {
&prefix,
FunctionKind::Any,
function_completion_kind,
false, // self_prefix
);
return;
}
Expand Down Expand Up @@ -340,6 +345,15 @@ impl<'a> NodeFinder<'a> {
self.local_variables_completion(&prefix);
self.builtin_functions_completion(&prefix, function_completion_kind);
self.builtin_values_completion(&prefix);
if let Some(self_type) = &self.self_type {
let self_prefix = true;
self.complete_type_fields_and_methods(
&self_type.clone(),
&prefix,
function_completion_kind,
self_prefix,
);
}
}
RequestedItems::OnlyTypes => {
self.builtin_types_completion(&prefix);
Expand Down Expand Up @@ -518,16 +532,18 @@ impl<'a> NodeFinder<'a> {
typ: &Type,
prefix: &str,
function_completion_kind: FunctionCompletionKind,
self_prefix: bool,
) {
match typ {
Type::Struct(struct_type, generics) => {
self.complete_struct_fields(&struct_type.borrow(), generics, prefix);
self.complete_struct_fields(&struct_type.borrow(), generics, prefix, self_prefix);
}
Type::MutableReference(typ) => {
return self.complete_type_fields_and_methods(
typ,
prefix,
function_completion_kind,
self_prefix,
);
}
Type::Alias(type_alias, _) => {
Expand All @@ -536,10 +552,11 @@ impl<'a> NodeFinder<'a> {
&type_alias.typ,
prefix,
function_completion_kind,
self_prefix,
);
}
Type::Tuple(types) => {
self.complete_tuple_fields(types);
self.complete_tuple_fields(types, self_prefix);
}
Type::FieldElement
| Type::Array(_, _)
Expand All @@ -565,6 +582,7 @@ impl<'a> NodeFinder<'a> {
prefix,
FunctionKind::SelfType(typ),
function_completion_kind,
self_prefix,
);
}

Expand All @@ -574,6 +592,7 @@ impl<'a> NodeFinder<'a> {
prefix: &str,
function_kind: FunctionKind,
function_completion_kind: FunctionCompletionKind,
self_prefix: bool,
) {
let Some(methods_by_name) = self.interner.get_type_methods(typ) else {
return;
Expand All @@ -587,6 +606,7 @@ impl<'a> NodeFinder<'a> {
func_id,
function_completion_kind,
function_kind,
self_prefix,
) {
self.completion_items.push(completion_item);
self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id));
Expand All @@ -603,13 +623,16 @@ impl<'a> NodeFinder<'a> {
function_kind: FunctionKind,
function_completion_kind: FunctionCompletionKind,
) {
let self_prefix = false;

for (name, func_id) in &trait_.method_ids {
if name_matches(name, prefix) {
if let Some(completion_item) = self.function_completion_item(
name,
*func_id,
function_completion_kind,
function_kind,
self_prefix,
) {
self.completion_items.push(completion_item);
self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id));
Expand All @@ -623,17 +646,19 @@ impl<'a> NodeFinder<'a> {
struct_type: &StructType,
generics: &[Type],
prefix: &str,
self_prefix: bool,
) {
for (name, typ) in &struct_type.get_fields(generics) {
if name_matches(name, prefix) {
self.completion_items.push(struct_field_completion_item(name, typ));
self.completion_items.push(struct_field_completion_item(name, typ, self_prefix));
}
}
}

fn complete_tuple_fields(&mut self, types: &[Type]) {
fn complete_tuple_fields(&mut self, types: &[Type], self_prefix: bool) {
for (index, typ) in types.iter().enumerate() {
self.completion_items.push(field_completion_item(&index.to_string(), typ.to_string()));
let name = index.to_string();
self.completion_items.push(field_completion_item(&name, typ.to_string(), self_prefix));
}
}

Expand Down Expand Up @@ -761,6 +786,23 @@ impl<'a> NodeFinder<'a> {
None
}

fn try_set_self_type(&mut self, pattern: &Pattern) {
match pattern {
Pattern::Identifier(ident) => {
if ident.0.contents == "self" {
let location = Location::new(ident.span(), self.file);
if let Some(ReferenceId::Local(definition_id)) =
self.interner.find_referenced(location)
{
self.self_type = Some(self.interner.definition_type(definition_id));
}
}
}
Pattern::Mutable(pattern, ..) => self.try_set_self_type(pattern),
Pattern::Tuple(..) | Pattern::Struct(..) => (),
}
}

fn includes_span(&self, span: Span) -> bool {
span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize
}
Expand Down Expand Up @@ -817,6 +859,7 @@ impl<'a> Visitor for NodeFinder<'a> {
self.collect_type_parameters_in_generics(&noir_function.def.generics);

for param in &noir_function.def.parameters {
self.try_set_self_type(&param.pattern);
param.typ.accept(self);
}

Expand All @@ -830,6 +873,7 @@ impl<'a> Visitor for NodeFinder<'a> {
noir_function.def.body.accept(Some(span), self);

self.type_parameters = old_type_parameters;
self.self_type = None;

false
}
Expand Down Expand Up @@ -945,7 +989,13 @@ impl<'a> Visitor for 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, FunctionCompletionKind::Name);
let self_prefix = false;
self.complete_type_fields_and_methods(
&typ,
prefix,
FunctionCompletionKind::Name,
self_prefix,
);
return false;
}
}
Expand Down Expand Up @@ -973,7 +1023,13 @@ impl<'a> Visitor for NodeFinder<'a> {
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);
let self_prefix = false;
self.complete_type_fields_and_methods(
&typ,
&prefix,
FunctionCompletionKind::Name,
self_prefix,
);
return false;
}
}
Expand Down Expand Up @@ -1042,10 +1098,12 @@ impl<'a> Visitor for NodeFinder<'a> {
{
let typ = self.interner.definition_type(definition_id);
let prefix = "";
let self_prefix = false;
self.complete_type_fields_and_methods(
&typ,
prefix,
FunctionCompletionKind::NameAndParameters,
self_prefix,
);
}
}
Expand All @@ -1072,10 +1130,12 @@ impl<'a> Visitor for NodeFinder<'a> {
if let Some(typ) = self.interner.type_at_location(location) {
let typ = typ.follow_bindings();
let prefix = "";
let self_prefix = false;
self.complete_type_fields_and_methods(
&typ,
prefix,
FunctionCompletionKind::NameAndParameters,
self_prefix,
);
}
}
Expand Down Expand Up @@ -1136,10 +1196,12 @@ impl<'a> Visitor for 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);
let self_prefix = false;
self.complete_type_fields_and_methods(
&typ,
&prefix,
FunctionCompletionKind::NameAndParameters,
self_prefix,
);
return false;
}
Expand Down
24 changes: 20 additions & 4 deletions tooling/lsp/src/requests/completion/completion_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ impl<'a> NodeFinder<'a> {
func_id,
function_completion_kind,
function_kind,
false, // self_prefix
),
ModuleDefId::TypeId(..) => Some(self.struct_completion_item(name)),
ModuleDefId::TypeAliasId(..) => Some(self.type_alias_completion_item(name)),
Expand Down Expand Up @@ -77,6 +78,7 @@ impl<'a> NodeFinder<'a> {
func_id: FuncId,
function_completion_kind: FunctionCompletionKind,
function_kind: FunctionKind,
self_prefix: bool,
) -> Option<CompletionItem> {
let func_meta = self.interner.function_meta(&func_id);

Expand Down Expand Up @@ -135,6 +137,8 @@ impl<'a> NodeFinder<'a> {
} else {
false
};
let name = if self_prefix { format!("self.{}", name) } else { name.clone() };
let name = &name;
let description = func_meta_type_to_string(func_meta, func_self_type.is_some());

let completion_item = match function_completion_kind {
Expand Down Expand Up @@ -294,12 +298,24 @@ fn type_to_self_string(typ: &Type, string: &mut String) {
}
}

pub(super) fn struct_field_completion_item(field: &str, typ: &Type) -> CompletionItem {
field_completion_item(field, typ.to_string())
pub(super) fn struct_field_completion_item(
field: &str,
typ: &Type,
self_type: bool,
) -> CompletionItem {
field_completion_item(field, typ.to_string(), self_type)
}

pub(super) fn field_completion_item(field: &str, typ: impl Into<String>) -> CompletionItem {
simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into()))
pub(super) fn field_completion_item(
field: &str,
typ: impl Into<String>,
self_type: bool,
) -> CompletionItem {
if self_type {
simple_completion_item(format!("self.{field}"), CompletionItemKind::FIELD, Some(typ.into()))
} else {
simple_completion_item(field, CompletionItemKind::FIELD, Some(typ.into()))
}
}

pub(super) fn simple_completion_item(
Expand Down
33 changes: 31 additions & 2 deletions tooling/lsp/src/requests/completion/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ mod completion_tests {
completion_items::{
completion_item_with_sort_text,
completion_item_with_trigger_parameter_hints_command, crate_completion_item,
field_completion_item, module_completion_item, simple_completion_item,
snippet_completion_item,
module_completion_item, simple_completion_item, snippet_completion_item,
},
sort_text::{auto_import_sort_text, self_mismatch_sort_text},
},
Expand Down Expand Up @@ -116,6 +115,10 @@ mod completion_tests {
))
}

fn field_completion_item(field: &str, typ: impl Into<String>) -> CompletionItem {
crate::requests::completion::field_completion_item(field, typ, false)
}

#[test]
async fn test_use_first_segment() {
let src = r#"
Expand Down Expand Up @@ -1888,4 +1891,30 @@ mod completion_tests {
Some("(use super::barbaz)".to_string()),
);
}

#[test]
async fn test_suggests_self_fields_and_methods() {
let src = r#"
struct Foo {
foobar: Field,
}
impl Foo {
fn foobarbaz(self) {}
fn some_method(self) {
foob>|<
}
}
"#;

assert_completion_excluding_auto_import(
src,
vec![
field_completion_item("self.foobar", "Field"),
function_completion_item("self.foobarbaz()", "self.foobarbaz()", "fn(self)"),
],
)
.await;
}
}

0 comments on commit f57ce85

Please sign in to comment.