Repository: noir-lang/noir (Rust)

Issue:
LSP suggests unavailable methods
### Aim

Given a struct with some `impl`s, the LSP suggests autocompletion with fns that are not available in the current context.

### Expected Behavior

Consider the following program

```rust
struct Foo<T> {
    t: T,
}

impl Foo<Field> {
    fn bar(_self: Self) -> Field {
        5
    }
}

impl Foo<u32> {
    fn bar(_self: Self) -> Field {
        5
    }

    fn baz(_self: Self) -> Field {
        6
    }
}

fn main() -> pub Field {
    let foo: Foo<Field> = Foo { t: 5 };
    foo.bar()
}
```

In the last line, only `bar()` can be called on `foo`.

### Bug

The LSP suggests incorrect autocompletes:

![Image](https://github.com/user-attachments/assets/12b5f6f5-c6aa-4c57-a1ce-1caf4f3dab5b)

Note that two versions of `bar` are suggested, one for each `impl`. However, the second one cannot be called since I have `Foo<Field>` and not `Foo<u32>`.

`baz` is also suggested as an alternative, but this is invalid: that method is not available on the type since it belongs to the other specialization.

Finally, `t` is suggested even though it is not public (and currently results in a warning).


Discussion:

Patch:
diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs
index 736d37fe83f..6d70ea2fd6d 100644
--- a/compiler/noirc_frontend/src/node_interner.rs
+++ b/compiler/noirc_frontend/src/node_interner.rs
@@ -2351,7 +2351,7 @@ impl Methods {
     }
 
     /// Select the 1 matching method with an object type matching `typ`
-    fn find_matching_method(
+    pub fn find_matching_method(
         &self,
         typ: &Type,
         has_self_param: bool,
diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs
index d71f0414adc..3762ba9cf8d 100644
--- a/tooling/lsp/src/requests/completion.rs
+++ b/tooling/lsp/src/requests/completion.rs
@@ -645,55 +645,53 @@ impl<'a> NodeFinder<'a> {
 
         let struct_id = get_type_struct_id(typ);
         let is_primitive = typ.is_primitive();
+        let has_self_param = matches!(function_kind, FunctionKind::SelfType(..));
 
         for (name, methods) in methods_by_name {
-            for (func_id, method_type) in methods.iter() {
-                if function_kind == FunctionKind::Any {
-                    if let Some(method_type) = method_type {
-                        if method_type.unify(typ).is_err() {
-                            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;
-                    }
-                }
+            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 {
+                continue;
+            };
 
-                if is_primitive
-                    && !method_call_is_visible(
-                        typ,
-                        func_id,
-                        self.module_id,
-                        self.interner,
-                        self.def_maps,
-                    )
-                {
+            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 name_matches(name, prefix) {
-                    let completion_items = self.function_completion_items(
-                        name,
-                        func_id,
-                        function_completion_kind,
-                        function_kind,
-                        None, // attribute first type
-                        self_prefix,
-                    );
-                    if !completion_items.is_empty() {
-                        self.completion_items.extend(completion_items);
-                        self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id));
-                    }
+            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,
+                    function_completion_kind,
+                    function_kind,
+                    None, // attribute first type
+                    self_prefix,
+                );
+                if !completion_items.is_empty() {
+                    self.completion_items.extend(completion_items);
+                    self.suggested_module_def_ids.insert(ModuleDefId::FunctionId(func_id));
                 }
             }
         }


Test patch:
diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs
index 8cfb2a4b5ee..9306e38a48a 100644
--- a/tooling/lsp/src/requests/completion/tests.rs
+++ b/tooling/lsp/src/requests/completion/tests.rs
@@ -2780,4 +2780,37 @@ fn main() {
         )
         .await;
     }
+
+    #[test]
+    async fn test_suggests_methods_based_on_type_generics() {
+        let src = r#"
+        struct Foo<T> {
+            t: T,
+        }
+
+        impl Foo<Field> {
+            fn bar_baz(_self: Self) -> Field {
+                5
+            }
+        }
+
+        impl Foo<u32> {
+            fn bar(_self: Self) -> Field {
+                5
+            }
+
+            fn baz(_self: Self) -> Field {
+                6
+            }
+        }
+
+        fn main() -> pub Field {
+            let foo: Foo<Field> = Foo { t: 5 };
+            foo.b>|<
+        }
+        "#;
+        let items = get_completions(src).await;
+        assert_eq!(items.len(), 1);
+        assert!(items[0].label == "bar_baz()");
+    }
 }