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

fix(lsp): suggest all possible trait methods, but only visible ones #7027

Merged
merged 13 commits into from
Jan 13, 2025
Merged
6 changes: 6 additions & 0 deletions compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1322,9 +1322,15 @@ impl<'context> Elaborator<'context> {
let span = trait_impl.object_type.span;
self.declare_methods_on_struct(Some(trait_id), &mut trait_impl.methods, span);

let trait_visibility = self.interner.get_trait(trait_id).visibility;

let methods = trait_impl.methods.function_ids();
for func_id in &methods {
self.interner.set_function_trait(*func_id, self_type.clone(), trait_id);

// A trait impl method has the same visibility as its trait
let modifiers = self.interner.function_modifiers_mut(func_id);
modifiers.visibility = trait_visibility;
}

let trait_generics = trait_impl.resolved_trait_generics.clone();
Expand Down
1 change: 1 addition & 0 deletions compiler/noirc_frontend/src/elaborator/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ impl<'context> Elaborator<'context> {
this.interner.update_trait(*trait_id, |trait_def| {
trait_def.set_trait_bounds(resolved_trait_bounds);
trait_def.set_where_clause(where_clause);
trait_def.set_visibility(unresolved_trait.trait_def.visibility);
});

let methods = this.resolve_trait_methods(*trait_id, unresolved_trait);
Expand Down
7 changes: 4 additions & 3 deletions compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,7 @@
// if it's an inline module, or the first char of a the file if it's an external module.
// - `location` will always point to the token "foo" in `mod foo` regardless of whether
// it's inline or external.
// Eventually the location put in `ModuleData` is used for codelenses about `contract`s,

Check warning on line 877 in compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (codelenses)
// so we keep using `location` so that it continues to work as usual.
let location = Location::new(mod_name.span(), mod_location.file);
let new_module = ModuleData::new(
Expand Down Expand Up @@ -1228,9 +1228,10 @@
for item in std::mem::take(&mut trait_impl.items) {
match item.item.kind {
TraitImplItemKind::Function(mut impl_method) => {
// Regardless of what visibility was on the source code, treat it as public
// (a warning is produced during parsing for this)
impl_method.def.visibility = ItemVisibility::Public;
// Set the impl method visibility as temporarily private.
// Eventually when we find out what trait is this impl for we'll set it
// to the trait's visibility.
impl_method.def.visibility = ItemVisibility::Private;

let func_id = interner.push_empty_fn();
let location = Location::new(impl_method.span(), file_id);
Expand Down
7 changes: 6 additions & 1 deletion compiler/noirc_frontend/src/hir_def/traits.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use iter_extended::vecmap;
use rustc_hash::FxHashMap as HashMap;

use crate::ast::{Ident, NoirFunction};
use crate::ast::{Ident, ItemVisibility, NoirFunction};
use crate::hir::type_check::generics::TraitGenerics;
use crate::ResolvedGeneric;
use crate::{
Expand Down Expand Up @@ -66,6 +66,7 @@ pub struct Trait {
pub name: Ident,
pub generics: Generics,
pub location: Location,
pub visibility: ItemVisibility,

/// When resolving the types of Trait elements, all references to `Self` resolve
/// to this TypeVariable. Then when we check if the types of trait impl elements
Expand Down Expand Up @@ -160,6 +161,10 @@ impl Trait {
self.where_clause = where_clause;
}

pub fn set_visibility(&mut self, visibility: ItemVisibility) {
self.visibility = visibility;
}

pub fn find_method(&self, name: &str) -> Option<TraitMethodId> {
for (idx, method) in self.methods.iter().enumerate() {
if &method.name == name {
Expand Down
40 changes: 18 additions & 22 deletions compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,12 @@
interned_statement_kinds: noirc_arena::Arena<StatementKind>,

// Interned `UnresolvedTypeData`s during comptime code.
interned_unresolved_type_datas: noirc_arena::Arena<UnresolvedTypeData>,

Check warning on line 217 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)

// Interned `Pattern`s during comptime code.
interned_patterns: noirc_arena::Arena<Pattern>,

/// Determins whether to run in LSP mode. In LSP mode references are tracked.

Check warning on line 222 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (Determins)
pub(crate) lsp_mode: bool,

/// Store the location of the references in the graph.
Expand Down Expand Up @@ -669,7 +669,7 @@
quoted_types: Default::default(),
interned_expression_kinds: Default::default(),
interned_statement_kinds: Default::default(),
interned_unresolved_type_datas: Default::default(),

Check warning on line 672 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)
interned_patterns: Default::default(),
lsp_mode: false,
location_indices: LocationIndices::default(),
Expand Down Expand Up @@ -729,6 +729,7 @@
crate_id: unresolved_trait.crate_id,
location: Location::new(unresolved_trait.trait_def.span, unresolved_trait.file_id),
generics,
visibility: ItemVisibility::Private,
self_type_typevar: TypeVariable::unbound(self.next_type_variable_id(), Kind::Normal),
methods: Vec::new(),
method_ids: unresolved_trait.method_ids.clone(),
Expand Down Expand Up @@ -2133,11 +2134,11 @@
&mut self,
typ: UnresolvedTypeData,
) -> InternedUnresolvedTypeData {
InternedUnresolvedTypeData(self.interned_unresolved_type_datas.insert(typ))

Check warning on line 2137 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)
}

pub fn get_unresolved_type_data(&self, id: InternedUnresolvedTypeData) -> &UnresolvedTypeData {
&self.interned_unresolved_type_datas[id.0]

Check warning on line 2141 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)
}

/// Returns the type of an operator (which is always a function), along with its return type.
Expand Down Expand Up @@ -2268,31 +2269,26 @@
}

/// Iterate through each method, starting with the direct methods
pub fn iter(&self) -> impl Iterator<Item = (FuncId, &Option<Type>)> + '_ {
let trait_impl_methods = self.trait_impl_methods.iter().map(|m| (m.method, &m.typ));
let direct = self.direct.iter().copied().map(|func_id| {
let typ: &Option<Type> = &None;
(func_id, typ)
});
pub fn iter(&self) -> impl Iterator<Item = (FuncId, Option<&Type>, Option<TraitId>)> {
let trait_impl_methods =
self.trait_impl_methods.iter().map(|m| (m.method, m.typ.as_ref(), Some(m.trait_id)));
let direct = self.direct.iter().copied().map(|func_id| (func_id, None, None));
direct.chain(trait_impl_methods)
}

/// Select the 1 matching method with an object type matching `typ`
pub fn find_matching_method(
&self,
typ: &Type,
pub fn find_matching_methods<'a>(
&'a self,
typ: &'a Type,
has_self_param: bool,
interner: &NodeInterner,
) -> Option<FuncId> {
// When adding methods we always check they do not overlap, so there should be
// at most 1 matching method in this list.
for (method, method_type) in self.iter() {
interner: &'a NodeInterner,
) -> impl Iterator<Item = (FuncId, Option<TraitId>)> + 'a {
self.iter().filter_map(move |(method, method_type, trait_id)| {
if Self::method_matches(typ, has_self_param, method, method_type, interner) {
return Some(method);
Some((method, trait_id))
} else {
None
}
}

None
})
}

pub fn find_direct_method(
Expand All @@ -2302,7 +2298,7 @@
interner: &NodeInterner,
) -> Option<FuncId> {
for method in &self.direct {
if Self::method_matches(typ, has_self_param, *method, &None, interner) {
if Self::method_matches(typ, has_self_param, *method, None, interner) {
return Some(*method);
}
}
Expand All @@ -2320,7 +2316,7 @@

for trait_impl_method in &self.trait_impl_methods {
let method = trait_impl_method.method;
let method_type = &trait_impl_method.typ;
let method_type = trait_impl_method.typ.as_ref();
let trait_id = trait_impl_method.trait_id;

if Self::method_matches(typ, has_self_param, method, method_type, interner) {
Expand All @@ -2335,7 +2331,7 @@
typ: &Type,
has_self_param: bool,
method: FuncId,
method_type: &Option<Type>,
method_type: Option<&Type>,
interner: &NodeInterner,
) -> bool {
match interner.function_meta(&method).typ.instantiate(interner).0 {
Expand Down
67 changes: 38 additions & 29 deletions tooling/lsp/src/requests/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId},
resolution::visibility::{
item_in_module_is_visible, method_call_is_visible, struct_member_is_visible,
trait_member_is_visible,
},
},
hir_def::traits::Trait,
Expand All @@ -46,7 +47,7 @@
use super::process_request;

mod auto_import;
mod builtins;

Check warning on line 50 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 @@ -245,7 +246,7 @@
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)
// (it could be that we are completing in the middle of an ident)
for segment in &path.segments {
let ident = &segment.ident;

Expand Down Expand Up @@ -658,39 +659,47 @@
let has_self_param = matches!(function_kind, FunctionKind::SelfType(..));

for (name, methods) in methods_by_name {
let Some(func_id) =
methods.find_matching_method(typ, has_self_param, self.interner).or_else(|| {
// Also try to find a method assuming typ is `&mut typ`:
// we want to suggest methods that take `&mut self` even though a variable might not
// be mutable, so a user can know they need to mark it as mutable.
let typ = Type::MutableReference(Box::new(typ.clone()));
methods.find_matching_method(&typ, has_self_param, self.interner)
})
else {
if !name_matches(name, prefix) {
continue;
};

if let Some(struct_id) = struct_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) {
continue;
}
}

if is_primitive
&& !method_call_is_visible(
typ,
func_id,
self.module_id,
self.interner,
self.def_maps,
)
for (func_id, trait_id) in
methods.find_matching_methods(typ, has_self_param, self.interner)
{
continue;
}
if let Some(struct_id) = struct_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,
) {
continue;
}
}

if let Some(trait_id) = trait_id {
let modifiers = self.interner.function_modifiers(&func_id);
let visibility = modifiers.visibility;
if !trait_member_is_visible(trait_id, visibility, self.module_id, self.def_maps)
{
continue;
}
}

if is_primitive
&& !method_call_is_visible(
typ,
func_id,
self.module_id,
self.interner,
self.def_maps,
)
{
continue;
}

if name_matches(name, prefix) {
let completion_items = self.function_completion_items(
name,
func_id,
Expand Down Expand Up @@ -1820,8 +1829,8 @@
///
/// For example:
///
/// // "merk" and "ro" match "merkle" and "root" and are in order

Check warning on line 1832 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 1833 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
51 changes: 51 additions & 0 deletions tooling/lsp/src/requests/completion/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
#[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>|<
"#;
Expand Down Expand Up @@ -2879,4 +2879,55 @@
let items = get_completions(src).await;
assert_eq!(items.len(), 1);
}

#[test]
async fn test_does_not_suggest_trait_function_not_visible() {
let src = r#"
mod moo {
trait Foo {
fn foobar();
}

impl Foo for Field {
fn foobar() {}
}
}

fn main() {
Field::fooba>|<
}

"#;
assert_completion(src, vec![]).await;
}

#[test]
async fn test_suggests_multiple_trait_methods() {
let src = r#"
mod moo {
pub trait Foo {
fn foobar();
}

impl Foo for Field {
fn foobar() {}
}

pub trait Bar {
fn foobar();
}

impl Bar for Field {
fn foobar() {}
}
}

fn main() {
Field::fooba>|<
}

"#;
let items = get_completions(src).await;
assert_eq!(items.len(), 2);
}
}
7 changes: 5 additions & 2 deletions tooling/lsp/src/requests/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,10 @@ fn format_function(id: FuncId, args: &ProcessRequestCallbackArgs) -> String {
}
string.push_str(" ");

if func_modifiers.visibility != ItemVisibility::Private {
if func_modifiers.visibility != ItemVisibility::Private
&& func_meta.trait_id.is_none()
&& func_meta.trait_impl.is_none()
{
string.push_str(&func_modifiers.visibility.to_string());
string.push(' ');
}
Expand Down Expand Up @@ -1154,7 +1157,7 @@ mod hover_tests {
assert!(hover_text.starts_with(
" two
impl<A> Bar<A, i32> for Foo<A>
pub fn bar_stuff(self)"
fn bar_stuff(self)"
));
}

Expand Down
Loading