Skip to content

Commit 7d46287

Browse files
asteritejfecher
andauthored
fix(lsp): suggest all possible trait methods, but only visible ones (#7027)
Co-authored-by: jfecher <jake@aztecprotocol.com>
1 parent 74d258f commit 7d46287

8 files changed

Lines changed: 129 additions & 57 deletions

File tree

compiler/noirc_frontend/src/elaborator/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,9 +1322,15 @@ impl<'context> Elaborator<'context> {
13221322
let span = trait_impl.object_type.span;
13231323
self.declare_methods_on_struct(Some(trait_id), &mut trait_impl.methods, span);
13241324

1325+
let trait_visibility = self.interner.get_trait(trait_id).visibility;
1326+
13251327
let methods = trait_impl.methods.function_ids();
13261328
for func_id in &methods {
13271329
self.interner.set_function_trait(*func_id, self_type.clone(), trait_id);
1330+
1331+
// A trait impl method has the same visibility as its trait
1332+
let modifiers = self.interner.function_modifiers_mut(func_id);
1333+
modifiers.visibility = trait_visibility;
13281334
}
13291335

13301336
let trait_generics = trait_impl.resolved_trait_generics.clone();

compiler/noirc_frontend/src/elaborator/traits.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ impl<'context> Elaborator<'context> {
5656
this.interner.update_trait(*trait_id, |trait_def| {
5757
trait_def.set_trait_bounds(resolved_trait_bounds);
5858
trait_def.set_where_clause(where_clause);
59+
trait_def.set_visibility(unresolved_trait.trait_def.visibility);
5960
});
6061

6162
let methods = this.resolve_trait_methods(*trait_id, unresolved_trait);

compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,9 +1228,10 @@ pub(crate) fn collect_trait_impl_items(
12281228
for item in std::mem::take(&mut trait_impl.items) {
12291229
match item.item.kind {
12301230
TraitImplItemKind::Function(mut impl_method) => {
1231-
// Regardless of what visibility was on the source code, treat it as public
1232-
// (a warning is produced during parsing for this)
1233-
impl_method.def.visibility = ItemVisibility::Public;
1231+
// Set the impl method visibility as temporarily private.
1232+
// Eventually when we find out what trait is this impl for we'll set it
1233+
// to the trait's visibility.
1234+
impl_method.def.visibility = ItemVisibility::Private;
12341235

12351236
let func_id = interner.push_empty_fn();
12361237
let location = Location::new(impl_method.span(), file_id);

compiler/noirc_frontend/src/hir_def/traits.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use iter_extended::vecmap;
22
use rustc_hash::FxHashMap as HashMap;
33

4-
use crate::ast::{Ident, NoirFunction};
4+
use crate::ast::{Ident, ItemVisibility, NoirFunction};
55
use crate::hir::type_check::generics::TraitGenerics;
66
use crate::ResolvedGeneric;
77
use crate::{
@@ -66,6 +66,7 @@ pub struct Trait {
6666
pub name: Ident,
6767
pub generics: Generics,
6868
pub location: Location,
69+
pub visibility: ItemVisibility,
6970

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

164+
pub fn set_visibility(&mut self, visibility: ItemVisibility) {
165+
self.visibility = visibility;
166+
}
167+
163168
pub fn find_method(&self, name: &str) -> Option<TraitMethodId> {
164169
for (idx, method) in self.methods.iter().enumerate() {
165170
if &method.name == name {

compiler/noirc_frontend/src/node_interner.rs

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ impl NodeInterner {
729729
crate_id: unresolved_trait.crate_id,
730730
location: Location::new(unresolved_trait.trait_def.span, unresolved_trait.file_id),
731731
generics,
732+
visibility: ItemVisibility::Private,
732733
self_type_typevar: TypeVariable::unbound(self.next_type_variable_id(), Kind::Normal),
733734
methods: Vec::new(),
734735
method_ids: unresolved_trait.method_ids.clone(),
@@ -2268,31 +2269,26 @@ impl Methods {
22682269
}
22692270

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

2280-
/// Select the 1 matching method with an object type matching `typ`
2281-
pub fn find_matching_method(
2282-
&self,
2283-
typ: &Type,
2279+
pub fn find_matching_methods<'a>(
2280+
&'a self,
2281+
typ: &'a Type,
22842282
has_self_param: bool,
2285-
interner: &NodeInterner,
2286-
) -> Option<FuncId> {
2287-
// When adding methods we always check they do not overlap, so there should be
2288-
// at most 1 matching method in this list.
2289-
for (method, method_type) in self.iter() {
2283+
interner: &'a NodeInterner,
2284+
) -> impl Iterator<Item = (FuncId, Option<TraitId>)> + 'a {
2285+
self.iter().filter_map(move |(method, method_type, trait_id)| {
22902286
if Self::method_matches(typ, has_self_param, method, method_type, interner) {
2291-
return Some(method);
2287+
Some((method, trait_id))
2288+
} else {
2289+
None
22922290
}
2293-
}
2294-
2295-
None
2291+
})
22962292
}
22972293

22982294
pub fn find_direct_method(
@@ -2302,7 +2298,7 @@ impl Methods {
23022298
interner: &NodeInterner,
23032299
) -> Option<FuncId> {
23042300
for method in &self.direct {
2305-
if Self::method_matches(typ, has_self_param, *method, &None, interner) {
2301+
if Self::method_matches(typ, has_self_param, *method, None, interner) {
23062302
return Some(*method);
23072303
}
23082304
}
@@ -2320,7 +2316,7 @@ impl Methods {
23202316

23212317
for trait_impl_method in &self.trait_impl_methods {
23222318
let method = trait_impl_method.method;
2323-
let method_type = &trait_impl_method.typ;
2319+
let method_type = trait_impl_method.typ.as_ref();
23242320
let trait_id = trait_impl_method.trait_id;
23252321

23262322
if Self::method_matches(typ, has_self_param, method, method_type, interner) {
@@ -2335,7 +2331,7 @@ impl Methods {
23352331
typ: &Type,
23362332
has_self_param: bool,
23372333
method: FuncId,
2338-
method_type: &Option<Type>,
2334+
method_type: Option<&Type>,
23392335
interner: &NodeInterner,
23402336
) -> bool {
23412337
match interner.function_meta(&method).typ.instantiate(interner).0 {

tooling/lsp/src/requests/completion.rs

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use noirc_frontend::{
2828
def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId},
2929
resolution::visibility::{
3030
item_in_module_is_visible, method_call_is_visible, struct_member_is_visible,
31+
trait_member_is_visible,
3132
},
3233
},
3334
hir_def::traits::Trait,
@@ -245,7 +246,7 @@ impl<'a> NodeFinder<'a> {
245246
let mut idents: Vec<Ident> = Vec::new();
246247

247248
// Find in which ident we are in, and in which part of it
248-
// (it could be that we are completting in the middle of an ident)
249+
// (it could be that we are completing in the middle of an ident)
249250
for segment in &path.segments {
250251
let ident = &segment.ident;
251252

@@ -658,39 +659,47 @@ impl<'a> NodeFinder<'a> {
658659
let has_self_param = matches!(function_kind, FunctionKind::SelfType(..));
659660

660661
for (name, methods) in methods_by_name {
661-
let Some(func_id) =
662-
methods.find_matching_method(typ, has_self_param, self.interner).or_else(|| {
663-
// Also try to find a method assuming typ is `&mut typ`:
664-
// we want to suggest methods that take `&mut self` even though a variable might not
665-
// be mutable, so a user can know they need to mark it as mutable.
666-
let typ = Type::MutableReference(Box::new(typ.clone()));
667-
methods.find_matching_method(&typ, has_self_param, self.interner)
668-
})
669-
else {
662+
if !name_matches(name, prefix) {
670663
continue;
671-
};
672-
673-
if let Some(struct_id) = struct_id {
674-
let modifiers = self.interner.function_modifiers(&func_id);
675-
let visibility = modifiers.visibility;
676-
if !struct_member_is_visible(struct_id, visibility, self.module_id, self.def_maps) {
677-
continue;
678-
}
679664
}
680665

681-
if is_primitive
682-
&& !method_call_is_visible(
683-
typ,
684-
func_id,
685-
self.module_id,
686-
self.interner,
687-
self.def_maps,
688-
)
666+
for (func_id, trait_id) in
667+
methods.find_matching_methods(typ, has_self_param, self.interner)
689668
{
690-
continue;
691-
}
669+
if let Some(struct_id) = struct_id {
670+
let modifiers = self.interner.function_modifiers(&func_id);
671+
let visibility = modifiers.visibility;
672+
if !struct_member_is_visible(
673+
struct_id,
674+
visibility,
675+
self.module_id,
676+
self.def_maps,
677+
) {
678+
continue;
679+
}
680+
}
681+
682+
if let Some(trait_id) = trait_id {
683+
let modifiers = self.interner.function_modifiers(&func_id);
684+
let visibility = modifiers.visibility;
685+
if !trait_member_is_visible(trait_id, visibility, self.module_id, self.def_maps)
686+
{
687+
continue;
688+
}
689+
}
690+
691+
if is_primitive
692+
&& !method_call_is_visible(
693+
typ,
694+
func_id,
695+
self.module_id,
696+
self.interner,
697+
self.def_maps,
698+
)
699+
{
700+
continue;
701+
}
692702

693-
if name_matches(name, prefix) {
694703
let completion_items = self.function_completion_items(
695704
name,
696705
func_id,

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,4 +2879,55 @@ fn main() {
28792879
let items = get_completions(src).await;
28802880
assert_eq!(items.len(), 1);
28812881
}
2882+
2883+
#[test]
2884+
async fn test_does_not_suggest_trait_function_not_visible() {
2885+
let src = r#"
2886+
mod moo {
2887+
trait Foo {
2888+
fn foobar();
2889+
}
2890+
2891+
impl Foo for Field {
2892+
fn foobar() {}
2893+
}
2894+
}
2895+
2896+
fn main() {
2897+
Field::fooba>|<
2898+
}
2899+
2900+
"#;
2901+
assert_completion(src, vec![]).await;
2902+
}
2903+
2904+
#[test]
2905+
async fn test_suggests_multiple_trait_methods() {
2906+
let src = r#"
2907+
mod moo {
2908+
pub trait Foo {
2909+
fn foobar();
2910+
}
2911+
2912+
impl Foo for Field {
2913+
fn foobar() {}
2914+
}
2915+
2916+
pub trait Bar {
2917+
fn foobar();
2918+
}
2919+
2920+
impl Bar for Field {
2921+
fn foobar() {}
2922+
}
2923+
}
2924+
2925+
fn main() {
2926+
Field::fooba>|<
2927+
}
2928+
2929+
"#;
2930+
let items = get_completions(src).await;
2931+
assert_eq!(items.len(), 2);
2932+
}
28822933
}

tooling/lsp/src/requests/hover.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,10 @@ fn format_function(id: FuncId, args: &ProcessRequestCallbackArgs) -> String {
399399
}
400400
string.push_str(" ");
401401

402-
if func_modifiers.visibility != ItemVisibility::Private {
402+
if func_modifiers.visibility != ItemVisibility::Private
403+
&& func_meta.trait_id.is_none()
404+
&& func_meta.trait_impl.is_none()
405+
{
403406
string.push_str(&func_modifiers.visibility.to_string());
404407
string.push(' ');
405408
}
@@ -1154,7 +1157,7 @@ mod hover_tests {
11541157
assert!(hover_text.starts_with(
11551158
" two
11561159
impl<A> Bar<A, i32> for Foo<A>
1157-
pub fn bar_stuff(self)"
1160+
fn bar_stuff(self)"
11581161
));
11591162
}
11601163

0 commit comments

Comments
 (0)