Skip to content

Commit fc75298

Browse files
authored
feat(LSP): suggest enum variants without parameters (noir-lang#7261)
1 parent 97073b2 commit fc75298

File tree

3 files changed

+127
-41
lines changed

3 files changed

+127
-41
lines changed

tooling/lsp/src/requests/completion.rs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{
22
collections::{BTreeMap, HashMap, HashSet},
33
future::{self, Future},
4+
ops::Deref,
45
};
56

67
use async_lsp::ResponseError;
@@ -199,15 +200,15 @@ impl<'a> NodeFinder<'a> {
199200
};
200201

201202
let location = Location::new(span, self.file);
202-
let Some(ReferenceId::Type(struct_id)) = self.interner.find_referenced(location) else {
203+
let Some(ReferenceId::Type(type_id)) = self.interner.find_referenced(location) else {
203204
return;
204205
};
205206

206-
let struct_type = self.interner.get_type(struct_id);
207-
let struct_type = struct_type.borrow();
207+
let data_type = self.interner.get_type(type_id);
208+
let data_type = data_type.borrow();
208209

209210
// First get all of the struct's fields
210-
let Some(fields) = struct_type.get_fields_as_written() else {
211+
let Some(fields) = data_type.get_fields_as_written() else {
211212
return;
212213
};
213214

@@ -223,7 +224,7 @@ impl<'a> NodeFinder<'a> {
223224
self.completion_items.push(self.struct_field_completion_item(
224225
&field.name.0.contents,
225226
&field.typ,
226-
struct_type.id,
227+
data_type.id,
227228
*field_index,
228229
self_prefix,
229230
));
@@ -320,10 +321,11 @@ impl<'a> NodeFinder<'a> {
320321

321322
match module_def_id {
322323
ModuleDefId::ModuleId(id) => module_id = id,
323-
ModuleDefId::TypeId(struct_id) => {
324-
let struct_type = self.interner.get_type(struct_id);
324+
ModuleDefId::TypeId(type_id) => {
325+
let data_type = self.interner.get_type(type_id);
326+
self.complete_enum_variants_without_parameters(&data_type.borrow(), &prefix);
325327
self.complete_type_methods(
326-
&Type::DataType(struct_type, vec![]),
328+
&Type::DataType(data_type, vec![]),
327329
&prefix,
328330
FunctionKind::Any,
329331
function_completion_kind,
@@ -657,7 +659,7 @@ impl<'a> NodeFinder<'a> {
657659
return;
658660
};
659661

660-
let struct_id = get_type_struct_id(typ);
662+
let type_id = get_type_type_id(typ);
661663
let is_primitive = typ.is_primitive();
662664
let has_self_param = matches!(function_kind, FunctionKind::SelfType(..));
663665

@@ -669,15 +671,11 @@ impl<'a> NodeFinder<'a> {
669671
for (func_id, trait_id) in
670672
methods.find_matching_methods(typ, has_self_param, self.interner)
671673
{
672-
if let Some(struct_id) = struct_id {
674+
if let Some(type_id) = type_id {
673675
let modifiers = self.interner.function_modifiers(&func_id);
674676
let visibility = modifiers.visibility;
675-
if !struct_member_is_visible(
676-
struct_id,
677-
visibility,
678-
self.module_id,
679-
self.def_maps,
680-
) {
677+
if !struct_member_is_visible(type_id, visibility, self.module_id, self.def_maps)
678+
{
681679
continue;
682680
}
683681
}
@@ -801,6 +799,23 @@ impl<'a> NodeFinder<'a> {
801799
}
802800
}
803801

802+
fn complete_enum_variants_without_parameters(&mut self, data_type: &DataType, prefix: &str) {
803+
let Some(variants) = data_type.get_variants_as_written() else {
804+
return;
805+
};
806+
807+
for (index, variant) in variants.iter().enumerate() {
808+
// Variants with parameters are represented as functions and are suggested in `complete_type_methods`
809+
if variant.is_function || !name_matches(&variant.name.0.contents, prefix) {
810+
continue;
811+
}
812+
813+
let item =
814+
self.enum_variant_completion_item(variant.name.to_string(), data_type.id, index);
815+
self.completion_items.push(item);
816+
}
817+
}
818+
804819
fn complete_struct_fields(
805820
&mut self,
806821
struct_type: &DataType,
@@ -1900,13 +1915,13 @@ fn get_array_element_type(typ: Type) -> Option<Type> {
19001915
}
19011916
}
19021917

1903-
fn get_type_struct_id(typ: &Type) -> Option<TypeId> {
1904-
match typ {
1918+
fn get_type_type_id(typ: &Type) -> Option<TypeId> {
1919+
match typ.follow_bindings_shallow().deref() {
19051920
Type::DataType(struct_type, _) => Some(struct_type.borrow().id),
19061921
Type::Alias(type_alias, generics) => {
19071922
let type_alias = type_alias.borrow();
19081923
let typ = type_alias.get_type(generics);
1909-
get_type_struct_id(&typ)
1924+
get_type_type_id(&typ)
19101925
}
19111926
_ => None,
19121927
}
@@ -1958,7 +1973,7 @@ fn name_matches(name: &str, prefix: &str) -> bool {
19581973
fn module_def_id_from_reference_id(reference_id: ReferenceId) -> Option<ModuleDefId> {
19591974
match reference_id {
19601975
ReferenceId::Module(module_id) => Some(ModuleDefId::ModuleId(module_id)),
1961-
ReferenceId::Type(struct_id) => Some(ModuleDefId::TypeId(struct_id)),
1976+
ReferenceId::Type(type_id) => Some(ModuleDefId::TypeId(type_id)),
19621977
ReferenceId::Trait(trait_id) => Some(ModuleDefId::TraitId(trait_id)),
19631978
ReferenceId::Function(func_id) => Some(ModuleDefId::FunctionId(func_id)),
19641979
ReferenceId::Alias(type_alias_id) => Some(ModuleDefId::TypeAliasId(type_alias_id)),

tooling/lsp/src/requests/completion/completion_items.rs

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,14 @@ impl<'a> NodeFinder<'a> {
8686
None, // trait_id
8787
false, // self_prefix
8888
),
89-
ModuleDefId::TypeId(struct_id) => vec![self.struct_completion_item(name, struct_id)],
89+
ModuleDefId::TypeId(type_id) => {
90+
let data_type = self.interner.get_type(type_id);
91+
if data_type.borrow().is_struct() {
92+
vec![self.struct_completion_item(name, type_id)]
93+
} else {
94+
vec![self.enum_completion_item(name, type_id)]
95+
}
96+
}
9097
ModuleDefId::TypeAliasId(id) => vec![self.type_alias_completion_item(name, id)],
9198
ModuleDefId::TraitId(trait_id) => vec![self.trait_completion_item(name, trait_id)],
9299
ModuleDefId::GlobalId(global_id) => vec![self.global_completion_item(name, global_id)],
@@ -106,14 +113,18 @@ impl<'a> NodeFinder<'a> {
106113
name: impl Into<String>,
107114
id: ModuleId,
108115
) -> CompletionItem {
109-
let completion_item = module_completion_item(name);
110-
self.completion_item_with_doc_comments(ReferenceId::Module(id), completion_item)
116+
let item = module_completion_item(name);
117+
self.completion_item_with_doc_comments(ReferenceId::Module(id), item)
111118
}
112119

113-
fn struct_completion_item(&self, name: String, struct_id: TypeId) -> CompletionItem {
114-
let completion_item =
115-
simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name));
116-
self.completion_item_with_doc_comments(ReferenceId::Type(struct_id), completion_item)
120+
fn struct_completion_item(&self, name: String, type_id: TypeId) -> CompletionItem {
121+
let items = simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name));
122+
self.completion_item_with_doc_comments(ReferenceId::Type(type_id), items)
123+
}
124+
125+
fn enum_completion_item(&self, name: String, type_id: TypeId) -> CompletionItem {
126+
let item = simple_completion_item(name.clone(), CompletionItemKind::ENUM, Some(name));
127+
self.completion_item_with_doc_comments(ReferenceId::Type(type_id), item)
117128
}
118129

119130
pub(super) fn struct_field_completion_item(
@@ -124,33 +135,42 @@ impl<'a> NodeFinder<'a> {
124135
field_index: usize,
125136
self_type: bool,
126137
) -> CompletionItem {
127-
let completion_item = struct_field_completion_item(field, typ, self_type);
128-
self.completion_item_with_doc_comments(
129-
ReferenceId::StructMember(struct_id, field_index),
130-
completion_item,
131-
)
138+
let item = struct_field_completion_item(field, typ, self_type);
139+
let reference_id = ReferenceId::StructMember(struct_id, field_index);
140+
self.completion_item_with_doc_comments(reference_id, item)
132141
}
133142

134143
fn type_alias_completion_item(&self, name: String, id: TypeAliasId) -> CompletionItem {
135-
let completion_item =
136-
simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name));
137-
self.completion_item_with_doc_comments(ReferenceId::Alias(id), completion_item)
144+
let item = simple_completion_item(name.clone(), CompletionItemKind::STRUCT, Some(name));
145+
self.completion_item_with_doc_comments(ReferenceId::Alias(id), item)
138146
}
139147

140148
fn trait_completion_item(&self, name: String, trait_id: TraitId) -> CompletionItem {
141-
let completion_item =
142-
simple_completion_item(name.clone(), CompletionItemKind::INTERFACE, Some(name));
143-
self.completion_item_with_doc_comments(ReferenceId::Trait(trait_id), completion_item)
149+
let item = simple_completion_item(name.clone(), CompletionItemKind::INTERFACE, Some(name));
150+
self.completion_item_with_doc_comments(ReferenceId::Trait(trait_id), item)
144151
}
145152

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

151-
let completion_item =
152-
simple_completion_item(name, CompletionItemKind::CONSTANT, Some(description));
153-
self.completion_item_with_doc_comments(ReferenceId::Global(global_id), completion_item)
161+
pub(super) fn enum_variant_completion_item(
162+
&self,
163+
name: String,
164+
type_id: TypeId,
165+
variant_index: usize,
166+
) -> CompletionItem {
167+
let kind = CompletionItemKind::ENUM_MEMBER;
168+
let item = simple_completion_item(name.clone(), kind, Some(name.clone()));
169+
let item = completion_item_with_detail(item, name);
170+
self.completion_item_with_doc_comments(
171+
ReferenceId::EnumVariant(type_id, variant_index),
172+
item,
173+
)
154174
}
155175

156176
#[allow(clippy::too_many_arguments)]
@@ -354,6 +374,8 @@ impl<'a> NodeFinder<'a> {
354374
if let (Some(type_id), Some(variant_index)) =
355375
(func_meta.type_id, func_meta.enum_variant_index)
356376
{
377+
completion_item.kind = Some(CompletionItemKind::ENUM_MEMBER);
378+
357379
self.completion_item_with_doc_comments(
358380
ReferenceId::EnumVariant(type_id, variant_index),
359381
completion_item,

tooling/lsp/src/requests/completion/tests.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3094,6 +3094,7 @@ fn main() {
30943094
assert_eq!(items.len(), 1);
30953095

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

30993100
let details = item.label_details.as_ref().unwrap();
@@ -3108,4 +3109,52 @@ fn main() {
31083109
};
31093110
assert!(markdown.value.contains("Some docs"));
31103111
}
3112+
3113+
#[test]
3114+
async fn test_suggests_enum_variant_without_parameters() {
3115+
let src = r#"
3116+
enum Enum {
3117+
/// Some docs
3118+
Variant
3119+
}
3120+
3121+
fn foo() {
3122+
Enum::Var>|<
3123+
}
3124+
"#;
3125+
let items = get_completions(src).await;
3126+
assert_eq!(items.len(), 1);
3127+
3128+
let item = &items[0];
3129+
assert_eq!(item.kind, Some(CompletionItemKind::ENUM_MEMBER));
3130+
assert_eq!(item.label, "Variant".to_string());
3131+
3132+
let details = item.label_details.as_ref().unwrap();
3133+
assert_eq!(details.description, Some("Variant".to_string()));
3134+
3135+
assert_eq!(item.detail, Some("Variant".to_string()));
3136+
assert_eq!(item.insert_text, None);
3137+
3138+
let Documentation::MarkupContent(markdown) = item.documentation.as_ref().unwrap() else {
3139+
panic!("Expected markdown docs");
3140+
};
3141+
assert!(markdown.value.contains("Some docs"));
3142+
}
3143+
3144+
#[test]
3145+
async fn test_suggests_enum_type() {
3146+
let src = r#"
3147+
enum ThisIsAnEnum {
3148+
}
3149+
3150+
fn foo() {
3151+
ThisIsA>|<
3152+
}
3153+
"#;
3154+
let items = get_completions(src).await;
3155+
assert_eq!(items.len(), 1);
3156+
3157+
let item = &items[0];
3158+
assert_eq!(item.kind, Some(CompletionItemKind::ENUM));
3159+
}
31113160
}

0 commit comments

Comments
 (0)