Skip to content

Commit

Permalink
feat: let LSP suggest macro calls too (#6090)
Browse files Browse the repository at this point in the history
# Description

## Problem

There was no way to get completions for macro calls, so the user had to
complete, go back and add the `!`.

## Summary

Now if something can be a macro call it's suggested too.

A special case is done for `unquote!`: it's only suggested as a macro
call, never as a function call.


![lsp-suggest-macros](https://github.com/user-attachments/assets/3bd6e38c-dfed-4391-b597-50feabef9539)

## 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.

---------

Co-authored-by: jfecher <jake@aztecprotocol.com>
  • Loading branch information
asterite and jfecher authored Sep 18, 2024
1 parent af52873 commit 26d275b
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 85 deletions.
30 changes: 17 additions & 13 deletions tooling/lsp/src/requests/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ impl<'a> NodeFinder<'a> {
}
Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => {
if let TypeBinding::Bound(typ) = &*var.borrow() {
self.complete_type_fields_and_methods(
return self.complete_type_fields_and_methods(
typ,
prefix,
function_completion_kind,
Expand Down Expand Up @@ -627,15 +627,16 @@ impl<'a> NodeFinder<'a> {
for (name, methods) in methods_by_name {
for func_id in methods.iter() {
if name_matches(name, prefix) {
if let Some(completion_item) = self.function_completion_item(
let completion_items = self.function_completion_items(
name,
func_id,
function_completion_kind,
function_kind,
None, // attribute first type
self_prefix,
) {
self.completion_items.push(completion_item);
);
if !completion_items.is_empty() {
self.completion_items.extend(completion_items);
self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id));
}
}
Expand All @@ -654,15 +655,16 @@ impl<'a> NodeFinder<'a> {

for (name, func_id) in &trait_.method_ids {
if name_matches(name, prefix) {
if let Some(completion_item) = self.function_completion_item(
let completion_items = self.function_completion_items(
name,
*func_id,
function_completion_kind,
function_kind,
None, // attribute first type
self_prefix,
) {
self.completion_items.push(completion_item);
);
if !completion_items.is_empty() {
self.completion_items.extend(completion_items);
self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(*func_id));
}
}
Expand Down Expand Up @@ -742,29 +744,31 @@ impl<'a> NodeFinder<'a> {
let per_ns = module_data.find_name(ident);
if let Some((module_def_id, visibility, _)) = per_ns.types {
if is_visible(module_id, self.module_id, visibility, self.def_maps) {
if let Some(completion_item) = self.module_def_id_completion_item(
let completion_items = self.module_def_id_completion_items(
module_def_id,
name.clone(),
function_completion_kind,
function_kind,
requested_items,
) {
self.completion_items.push(completion_item);
);
if !completion_items.is_empty() {
self.completion_items.extend(completion_items);
self.suggested_module_def_ids.insert(module_def_id);
}
}
}

if let Some((module_def_id, visibility, _)) = per_ns.values {
if is_visible(module_id, self.module_id, visibility, self.def_maps) {
if let Some(completion_item) = self.module_def_id_completion_item(
let completion_items = self.module_def_id_completion_items(
module_def_id,
name.clone(),
function_completion_kind,
function_kind,
requested_items,
) {
self.completion_items.push(completion_item);
);
if !completion_items.is_empty() {
self.completion_items.extend(completion_items);
self.suggested_module_def_ids.insert(module_def_id);
}
}
Expand Down
105 changes: 55 additions & 50 deletions tooling/lsp/src/requests/completion/auto_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,73 +29,78 @@ impl<'a> NodeFinder<'a> {
continue;
}

let Some(mut completion_item) = self.module_def_id_completion_item(
let completion_items = self.module_def_id_completion_items(
*module_def_id,
name.clone(),
function_completion_kind,
FunctionKind::Any,
requested_items,
) else {
);

if completion_items.is_empty() {
continue;
};

let module_full_path = if let Some(defining_module) = defining_module {
relative_module_id_path(
*defining_module,
&self.module_id,
current_module_parent_id,
self.interner,
)
} else {
let Some(module_full_path) = relative_module_full_path(
*module_def_id,
*visibility,
self.module_id,
current_module_parent_id,
self.interner,
self.def_maps,
) else {
continue;
self.suggested_module_def_ids.insert(*module_def_id);

for mut completion_item in completion_items {
let module_full_path = if let Some(defining_module) = defining_module {
relative_module_id_path(
*defining_module,
&self.module_id,
current_module_parent_id,
self.interner,
)
} else {
let Some(module_full_path) = relative_module_full_path(
*module_def_id,
*visibility,
self.module_id,
current_module_parent_id,
self.interner,
self.def_maps,
) else {
continue;
};
module_full_path
};
module_full_path
};

let full_path = if defining_module.is_some()
|| !matches!(module_def_id, ModuleDefId::ModuleId(..))
{
format!("{}::{}", module_full_path, name)
} else {
module_full_path
};
let full_path = if defining_module.is_some()
|| !matches!(module_def_id, ModuleDefId::ModuleId(..))
{
format!("{}::{}", module_full_path, name)
} else {
module_full_path
};

let mut label_details = completion_item.label_details.unwrap();
label_details.detail = Some(format!("(use {})", full_path));
completion_item.label_details = Some(label_details);
let mut label_details = completion_item.label_details.unwrap();
label_details.detail = Some(format!("(use {})", full_path));
completion_item.label_details = Some(label_details);

let line = self.auto_import_line as u32;
let character = (self.nesting * 4) as u32;
let indent = " ".repeat(self.nesting * 4);
let mut newlines = "\n";
let line = self.auto_import_line as u32;
let character = (self.nesting * 4) as u32;
let indent = " ".repeat(self.nesting * 4);
let mut newlines = "\n";

// If the line we are inserting into is not an empty line, insert an extra line to make some room
if let Some(line_text) = self.lines.get(line as usize) {
if !line_text.trim().is_empty() {
newlines = "\n\n";
// If the line we are inserting into is not an empty line, insert an extra line to make some room
if let Some(line_text) = self.lines.get(line as usize) {
if !line_text.trim().is_empty() {
newlines = "\n\n";
}
}
}

completion_item.additional_text_edits = Some(vec![TextEdit {
range: Range {
start: Position { line, character },
end: Position { line, character },
},
new_text: format!("use {};{}{}", full_path, newlines, indent),
}]);
completion_item.additional_text_edits = Some(vec![TextEdit {
range: Range {
start: Position { line, character },
end: Position { line, character },
},
new_text: format!("use {};{}{}", full_path, newlines, indent),
}]);

completion_item.sort_text = Some(auto_import_sort_text());
completion_item.sort_text = Some(auto_import_sort_text());

self.completion_items.push(completion_item);
self.suggested_module_def_ids.insert(*module_def_id);
self.completion_items.push(completion_item);
}
}
}
}
Expand Down
87 changes: 67 additions & 20 deletions tooling/lsp/src/requests/completion/completion_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,25 @@ use super::{
};

impl<'a> NodeFinder<'a> {
pub(super) fn module_def_id_completion_item(
pub(super) fn module_def_id_completion_items(
&self,
module_def_id: ModuleDefId,
name: String,
function_completion_kind: FunctionCompletionKind,
function_kind: FunctionKind,
requested_items: RequestedItems,
) -> Option<CompletionItem> {
) -> Vec<CompletionItem> {
match requested_items {
RequestedItems::OnlyTypes => match module_def_id {
ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return None,
ModuleDefId::FunctionId(_) | ModuleDefId::GlobalId(_) => return Vec::new(),
ModuleDefId::ModuleId(_)
| ModuleDefId::TypeId(_)
| ModuleDefId::TypeAliasId(_)
| ModuleDefId::TraitId(_) => (),
},
RequestedItems::OnlyAttributeFunctions(..) => {
if !matches!(module_def_id, ModuleDefId::FunctionId(..)) {
return None;
return Vec::new();
}
}
RequestedItems::AnyItems => (),
Expand All @@ -57,19 +57,19 @@ impl<'a> NodeFinder<'a> {
};

match module_def_id {
ModuleDefId::ModuleId(id) => Some(self.module_completion_item(name, id)),
ModuleDefId::FunctionId(func_id) => self.function_completion_item(
ModuleDefId::ModuleId(id) => vec![self.module_completion_item(name, id)],
ModuleDefId::FunctionId(func_id) => self.function_completion_items(
&name,
func_id,
function_completion_kind,
function_kind,
attribute_first_type.as_ref(),
false, // self_prefix
),
ModuleDefId::TypeId(struct_id) => Some(self.struct_completion_item(name, struct_id)),
ModuleDefId::TypeAliasId(id) => Some(self.type_alias_completion_item(name, id)),
ModuleDefId::TraitId(trait_id) => Some(self.trait_completion_item(name, trait_id)),
ModuleDefId::GlobalId(global_id) => Some(self.global_completion_item(name, global_id)),
ModuleDefId::TypeId(struct_id) => vec![self.struct_completion_item(name, struct_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 Down Expand Up @@ -133,15 +133,15 @@ impl<'a> NodeFinder<'a> {
self.completion_item_with_doc_comments(ReferenceId::Global(global_id), completion_item)
}

pub(super) fn function_completion_item(
pub(super) fn function_completion_items(
&self,
name: &String,
func_id: FuncId,
function_completion_kind: FunctionCompletionKind,
function_kind: FunctionKind,
attribute_first_type: Option<&Type>,
self_prefix: bool,
) -> Option<CompletionItem> {
) -> Vec<CompletionItem> {
let func_meta = self.interner.function_meta(&func_id);

let func_self_type = if let Some((pattern, typ, _)) = func_meta.parameters.0.first() {
Expand All @@ -161,12 +161,12 @@ impl<'a> NodeFinder<'a> {

if let Some(attribute_first_type) = attribute_first_type {
if func_meta.parameters.is_empty() {
return None;
return Vec::new();
}

let (_, typ, _) = &func_meta.parameters.0[0];
if typ != attribute_first_type {
return None;
return Vec::new();
}
}

Expand All @@ -186,23 +186,71 @@ impl<'a> NodeFinder<'a> {
}

if self_type != func_self_type {
return None;
return Vec::new();
}
} else if let Type::Tuple(self_tuple_types) = self_type {
// Tuple types of different lengths seem to also have methods defined on all of them,
// so here we reject methods for tuples where the length doesn't match.
if let Type::Tuple(func_self_tuple_types) = func_self_type {
if self_tuple_types.len() != func_self_tuple_types.len() {
return None;
return Vec::new();
}
}
}
} else {
return None;
return Vec::new();
}
}
}

let make_completion_item = |is_macro_call| {
self.function_completion_item(
name,
func_id,
func_meta,
func_self_type,
function_completion_kind,
function_kind,
attribute_first_type,
self_prefix,
is_macro_call,
)
};

// When suggesting functions in attributes, never suggest a macro call
if attribute_first_type.is_some() {
return vec![make_completion_item(false)];
}

// Special case: the `unquote` macro
// (it's unlikely users will define a function named `unquote` that does something different than std's unquote)
if name == "unquote" {
return vec![make_completion_item(true)];
}

let modifiers = self.interner.function_modifiers(&func_id);
if modifiers.is_comptime
&& matches!(func_meta.return_type(), Type::Quoted(QuotedType::Quoted))
{
vec![make_completion_item(false), make_completion_item(true)]
} else {
vec![make_completion_item(false)]
}
}

#[allow(clippy::too_many_arguments)]
pub(super) fn function_completion_item(
&self,
name: &String,
func_id: FuncId,
func_meta: &FuncMeta,
func_self_type: Option<&Type>,
function_completion_kind: FunctionCompletionKind,
function_kind: FunctionKind,
attribute_first_type: Option<&Type>,
self_prefix: bool,
is_macro_call: bool,
) -> CompletionItem {
let is_operator = if let Some(trait_impl_id) = &func_meta.trait_impl {
let trait_impl = self.interner.get_trait_implementation(*trait_impl_id);
let trait_impl = trait_impl.borrow();
Expand All @@ -211,6 +259,7 @@ impl<'a> NodeFinder<'a> {
false
};
let name = if self_prefix { format!("self.{}", name) } else { name.clone() };
let name = if is_macro_call { format!("{}!", name) } else { name };
let name = &name;
let description = func_meta_type_to_string(func_meta, func_self_type.is_some());
let mut has_arguments = false;
Expand Down Expand Up @@ -269,10 +318,8 @@ impl<'a> NodeFinder<'a> {
}
}
};
let completion_item =
self.completion_item_with_doc_comments(ReferenceId::Function(func_id), completion_item);

Some(completion_item)
self.completion_item_with_doc_comments(ReferenceId::Function(func_id), completion_item)
}

fn compute_function_insert_text(
Expand Down
Loading

0 comments on commit 26d275b

Please sign in to comment.