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): suggest enum variants without parameters #7261

Merged
merged 9 commits into from
Feb 3, 2025
Merged
55 changes: 35 additions & 20 deletions tooling/lsp/src/requests/completion.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
collections::{BTreeMap, HashMap, HashSet},
future::{self, Future},
ops::Deref,
};

use async_lsp::ResponseError;
Expand Down Expand Up @@ -47,7 +48,7 @@
use super::{process_request, TraitReexport};

mod auto_import;
mod builtins;

Check warning on line 51 in tooling/lsp/src/requests/completion.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (builtins)
mod completion_items;
mod kinds;
mod sort_text;
Expand Down Expand Up @@ -199,15 +200,15 @@
};

let location = Location::new(span, self.file);
let Some(ReferenceId::Type(struct_id)) = self.interner.find_referenced(location) else {
let Some(ReferenceId::Type(type_id)) = self.interner.find_referenced(location) else {
return;
};

let struct_type = self.interner.get_type(struct_id);
let struct_type = struct_type.borrow();
let data_type = self.interner.get_type(type_id);
let data_type = data_type.borrow();

// First get all of the struct's fields
let Some(fields) = struct_type.get_fields_as_written() else {
let Some(fields) = data_type.get_fields_as_written() else {
return;
};

Expand All @@ -223,7 +224,7 @@
self.completion_items.push(self.struct_field_completion_item(
&field.name.0.contents,
&field.typ,
struct_type.id,
data_type.id,
*field_index,
self_prefix,
));
Expand Down Expand Up @@ -320,10 +321,11 @@

match module_def_id {
ModuleDefId::ModuleId(id) => module_id = id,
ModuleDefId::TypeId(struct_id) => {
let struct_type = self.interner.get_type(struct_id);
ModuleDefId::TypeId(type_id) => {
let data_type = self.interner.get_type(type_id);
self.complete_enum_variants_without_parameters(&data_type.borrow(), &prefix);
self.complete_type_methods(
&Type::DataType(struct_type, vec![]),
&Type::DataType(data_type, vec![]),
&prefix,
FunctionKind::Any,
function_completion_kind,
Expand Down Expand Up @@ -657,7 +659,7 @@
return;
};

let struct_id = get_type_struct_id(typ);
let type_id = get_type_type_id(typ);
let is_primitive = typ.is_primitive();
let has_self_param = matches!(function_kind, FunctionKind::SelfType(..));

Expand All @@ -669,15 +671,11 @@
for (func_id, trait_id) in
methods.find_matching_methods(typ, has_self_param, self.interner)
{
if let Some(struct_id) = struct_id {
if let Some(type_id) = type_id {
let modifiers = self.interner.function_modifiers(&func_id);
let visibility = modifiers.visibility;
if !struct_member_is_visible(
struct_id,
visibility,
self.module_id,
self.def_maps,
) {
if !struct_member_is_visible(type_id, visibility, self.module_id, self.def_maps)
{
continue;
}
}
Expand Down Expand Up @@ -801,6 +799,23 @@
}
}

fn complete_enum_variants_without_parameters(&mut self, data_type: &DataType, prefix: &str) {
let Some(variants) = data_type.get_variants_as_written() else {
return;
};

for (index, variant) in variants.iter().enumerate() {
// Variants with parameters are represented as functions and are suggested in `complete_type_methods`
if variant.is_function || !name_matches(&variant.name.0.contents, prefix) {
continue;
}

let item =
self.enum_variant_completion_item(variant.name.to_string(), data_type.id, index);
self.completion_items.push(item);
}
}

fn complete_struct_fields(
&mut self,
struct_type: &DataType,
Expand Down Expand Up @@ -1118,7 +1133,7 @@

/// Try to suggest the name of a module to declare based on which
/// files exist in the filesystem, excluding modules that are already declared.
fn complete_module_delcaration(&mut self, module: &ModuleDeclaration) -> Option<()> {

Check warning on line 1136 in tooling/lsp/src/requests/completion.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (delcaration)
let filename = self.files.get_absolute_name(self.file).ok()?.into_path_buf();

let is_main_lib_or_mod = filename.ends_with("main.nr")
Expand Down Expand Up @@ -1854,7 +1869,7 @@
return;
}

self.complete_module_delcaration(module);

Check warning on line 1872 in tooling/lsp/src/requests/completion.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (delcaration)
}
}

Expand Down Expand Up @@ -1900,13 +1915,13 @@
}
}

fn get_type_struct_id(typ: &Type) -> Option<TypeId> {
match typ {
fn get_type_type_id(typ: &Type) -> Option<TypeId> {
asterite marked this conversation as resolved.
Show resolved Hide resolved
match typ.follow_bindings_shallow().deref() {
Type::DataType(struct_type, _) => Some(struct_type.borrow().id),
Type::Alias(type_alias, generics) => {
let type_alias = type_alias.borrow();
let typ = type_alias.get_type(generics);
get_type_struct_id(&typ)
get_type_type_id(&typ)
}
_ => None,
}
Expand All @@ -1920,8 +1935,8 @@
///
/// For example:
///
/// // "merk" and "ro" match "merkle" and "root" and are in order

Check warning on line 1938 in tooling/lsp/src/requests/completion.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (merk)
/// name_matches("compute_merkle_root", "merk_ro") == true

Check warning on line 1939 in tooling/lsp/src/requests/completion.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (merk)
///
/// // "ro" matches "root", but "merkle" comes before it, so no match
/// name_matches("compute_merkle_root", "ro_mer") == false
Expand Down Expand Up @@ -1958,7 +1973,7 @@
fn module_def_id_from_reference_id(reference_id: ReferenceId) -> Option<ModuleDefId> {
match reference_id {
ReferenceId::Module(module_id) => Some(ModuleDefId::ModuleId(module_id)),
ReferenceId::Type(struct_id) => Some(ModuleDefId::TypeId(struct_id)),
ReferenceId::Type(type_id) => Some(ModuleDefId::TypeId(type_id)),
ReferenceId::Trait(trait_id) => Some(ModuleDefId::TraitId(trait_id)),
ReferenceId::Function(func_id) => Some(ModuleDefId::FunctionId(func_id)),
ReferenceId::Alias(type_alias_id) => Some(ModuleDefId::TypeAliasId(type_alias_id)),
Expand Down
64 changes: 43 additions & 21 deletions tooling/lsp/src/requests/completion/completion_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,14 @@ impl<'a> NodeFinder<'a> {
None, // trait_id
false, // self_prefix
),
ModuleDefId::TypeId(struct_id) => vec![self.struct_completion_item(name, struct_id)],
ModuleDefId::TypeId(type_id) => {
let data_type = self.interner.get_type(type_id);
if data_type.borrow().is_struct() {
vec![self.struct_completion_item(name, type_id)]
} else {
vec![self.enum_completion_item(name, type_id)]
}
}
ModuleDefId::TypeAliasId(id) => vec![self.type_alias_completion_item(name, id)],
ModuleDefId::TraitId(trait_id) => vec![self.trait_completion_item(name, trait_id)],
ModuleDefId::GlobalId(global_id) => vec![self.global_completion_item(name, global_id)],
Expand All @@ -106,14 +113,18 @@ impl<'a> NodeFinder<'a> {
name: impl Into<String>,
id: ModuleId,
) -> CompletionItem {
let completion_item = module_completion_item(name);
self.completion_item_with_doc_comments(ReferenceId::Module(id), completion_item)
let item = module_completion_item(name);
self.completion_item_with_doc_comments(ReferenceId::Module(id), item)
}

fn struct_completion_item(&self, name: String, struct_id: TypeId) -> CompletionItem {
let completion_item =
simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name));
self.completion_item_with_doc_comments(ReferenceId::Type(struct_id), completion_item)
fn struct_completion_item(&self, name: String, type_id: TypeId) -> CompletionItem {
let items = simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name));
self.completion_item_with_doc_comments(ReferenceId::Type(type_id), items)
}

fn enum_completion_item(&self, name: String, type_id: TypeId) -> CompletionItem {
let item = simple_completion_item(name.clone(), CompletionItemKind::ENUM, Some(name));
self.completion_item_with_doc_comments(ReferenceId::Type(type_id), item)
}

pub(super) fn struct_field_completion_item(
Expand All @@ -124,33 +135,42 @@ impl<'a> NodeFinder<'a> {
field_index: usize,
self_type: bool,
) -> CompletionItem {
let completion_item = struct_field_completion_item(field, typ, self_type);
self.completion_item_with_doc_comments(
ReferenceId::StructMember(struct_id, field_index),
completion_item,
)
let item = struct_field_completion_item(field, typ, self_type);
let reference_id = ReferenceId::StructMember(struct_id, field_index);
self.completion_item_with_doc_comments(reference_id, item)
}

fn type_alias_completion_item(&self, name: String, id: TypeAliasId) -> CompletionItem {
let completion_item =
simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name));
self.completion_item_with_doc_comments(ReferenceId::Alias(id), completion_item)
let item = simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name));
self.completion_item_with_doc_comments(ReferenceId::Alias(id), item)
}

fn trait_completion_item(&self, name: String, trait_id: TraitId) -> CompletionItem {
let completion_item =
simple_completion_item(name.clone(), CompletionItemKind::INTERFACE, Some(name));
self.completion_item_with_doc_comments(ReferenceId::Trait(trait_id), completion_item)
let item = simple_completion_item(name.clone(), CompletionItemKind::INTERFACE, Some(name));
self.completion_item_with_doc_comments(ReferenceId::Trait(trait_id), item)
}

fn global_completion_item(&self, name: String, global_id: GlobalId) -> CompletionItem {
let global = self.interner.get_global(global_id);
let typ = self.interner.definition_type(global.definition_id);
let description = typ.to_string();
let item = simple_completion_item(name, CompletionItemKind::CONSTANT, Some(description));
self.completion_item_with_doc_comments(ReferenceId::Global(global_id), item)
}

let completion_item =
simple_completion_item(name, CompletionItemKind::CONSTANT, Some(description));
self.completion_item_with_doc_comments(ReferenceId::Global(global_id), completion_item)
pub(super) fn enum_variant_completion_item(
&self,
name: String,
type_id: TypeId,
variant_index: usize,
) -> CompletionItem {
let kind = CompletionItemKind::ENUM_MEMBER;
let item = simple_completion_item(name.clone(), kind, Some(name.clone()));
let item = completion_item_with_detail(item, name);
self.completion_item_with_doc_comments(
ReferenceId::EnumVariant(type_id, variant_index),
item,
)
}

#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -354,6 +374,8 @@ impl<'a> NodeFinder<'a> {
if let (Some(type_id), Some(variant_index)) =
(func_meta.type_id, func_meta.enum_variant_index)
{
completion_item.kind = Some(CompletionItemKind::ENUM_MEMBER);

self.completion_item_with_doc_comments(
ReferenceId::EnumVariant(type_id, variant_index),
completion_item,
Expand Down
49 changes: 49 additions & 0 deletions tooling/lsp/src/requests/completion/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,14 @@
#[test]
async fn test_use_first_segment() {
let src = r#"
mod foobaz {}

Check warning on line 137 in tooling/lsp/src/requests/completion/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (foobaz)
mod foobar {}
use foob>|<

Check warning on line 139 in tooling/lsp/src/requests/completion/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (foob)
"#;

assert_completion(
src,
vec![module_completion_item("foobaz"), module_completion_item("foobar")],

Check warning on line 144 in tooling/lsp/src/requests/completion/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (foobaz)
)
.await;
}
Expand Down Expand Up @@ -304,7 +304,7 @@
mod bar {
mod something {}

use super::foob>|<

Check warning on line 307 in tooling/lsp/src/requests/completion/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (foob)
}
"#;

Expand Down Expand Up @@ -1716,7 +1716,7 @@
#[test]
async fn test_auto_import_suggests_modules_too() {
let src = r#"mod foo {
pub mod barbaz {

Check warning on line 1719 in tooling/lsp/src/requests/completion/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (barbaz)
fn hello_world() {}
}
}
Expand Down Expand Up @@ -3094,6 +3094,7 @@
assert_eq!(items.len(), 1);

let item = &items[0];
assert_eq!(item.kind, Some(CompletionItemKind::ENUM_MEMBER));
assert_eq!(item.label, "Variant(…)".to_string());

let details = item.label_details.as_ref().unwrap();
Expand All @@ -3108,4 +3109,52 @@
};
assert!(markdown.value.contains("Some docs"));
}

#[test]
async fn test_suggests_enum_variant_without_parameters() {
let src = r#"
enum Enum {
/// Some docs
Variant
}

fn foo() {
Enum::Var>|<
}
"#;
let items = get_completions(src).await;
assert_eq!(items.len(), 1);

let item = &items[0];
assert_eq!(item.kind, Some(CompletionItemKind::ENUM_MEMBER));
assert_eq!(item.label, "Variant".to_string());

let details = item.label_details.as_ref().unwrap();
assert_eq!(details.description, Some("Variant".to_string()));

assert_eq!(item.detail, Some("Variant".to_string()));
assert_eq!(item.insert_text, None);

let Documentation::MarkupContent(markdown) = item.documentation.as_ref().unwrap() else {
panic!("Expected markdown docs");
};
assert!(markdown.value.contains("Some docs"));
}

#[test]
async fn test_suggests_enum_type() {
let src = r#"
enum ThisIsAnEnum {
}

fn foo() {
ThisIsA>|<
}
"#;
let items = get_completions(src).await;
assert_eq!(items.len(), 1);

let item = &items[0];
assert_eq!(item.kind, Some(CompletionItemKind::ENUM));
}
}
Loading