Skip to content

Commit 3b8d1da

Browse files
authored
feat(LSP): code action to import trait in a method call (#7066)
1 parent 14a7e37 commit 3b8d1da

File tree

3 files changed

+256
-1
lines changed

3 files changed

+256
-1
lines changed

tooling/lsp/src/requests/code_action.rs

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use super::{process_request, to_lsp_location};
3333
mod fill_struct_fields;
3434
mod implement_missing_members;
3535
mod import_or_qualify;
36+
mod import_trait;
3637
mod remove_bang_from_call;
3738
mod remove_unused_import;
3839
mod tests;
@@ -285,6 +286,8 @@ impl<'a> Visitor for CodeActionFinder<'a> {
285286
self.remove_bang_from_call(method_call.method_name.span());
286287
}
287288

289+
self.import_trait_in_method_call(method_call);
290+
288291
true
289292
}
290293
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
use noirc_errors::Location;
2+
use noirc_frontend::{
3+
ast::MethodCallExpression,
4+
hir::{def_map::ModuleDefId, resolution::visibility::trait_member_is_visible},
5+
hir_def::traits::Trait,
6+
node_interner::ReferenceId,
7+
};
8+
9+
use crate::{
10+
modules::relative_module_full_path,
11+
use_segment_positions::{
12+
use_completion_item_additional_text_edits, UseCompletionItemAdditionTextEditsRequest,
13+
},
14+
};
15+
16+
use super::CodeActionFinder;
17+
18+
impl<'a> CodeActionFinder<'a> {
19+
pub(super) fn import_trait_in_method_call(&mut self, method_call: &MethodCallExpression) {
20+
// First see if the method name already points to a function.
21+
let name_location = Location::new(method_call.method_name.span(), self.file);
22+
if let Some(ReferenceId::Function(func_id)) = self.interner.find_referenced(name_location) {
23+
// If yes, it could be that the compiler is issuing a warning because there's
24+
// only one possible trait that the method could be coming from, but it's not imported
25+
let func_meta = self.interner.function_meta(&func_id);
26+
let Some(trait_impl_id) = func_meta.trait_impl else {
27+
return;
28+
};
29+
30+
let trait_impl = self.interner.get_trait_implementation(trait_impl_id);
31+
let trait_id = trait_impl.borrow().trait_id;
32+
let trait_ = self.interner.get_trait(trait_id);
33+
34+
// Check if the trait is currently imported. If so, no need to suggest anything
35+
let module_data =
36+
&self.def_maps[&self.module_id.krate].modules()[self.module_id.local_id.0];
37+
if !module_data.scope().find_name(&trait_.name).is_none() {
38+
return;
39+
}
40+
41+
self.push_import_trait_code_action(trait_);
42+
return;
43+
}
44+
45+
// Find out the type of the object
46+
let object_location = Location::new(method_call.object.span, self.file);
47+
let Some(typ) = self.interner.type_at_location(object_location) else {
48+
return;
49+
};
50+
51+
for (func_id, trait_id) in
52+
self.interner.lookup_trait_methods(&typ, &method_call.method_name.0.contents, true)
53+
{
54+
let visibility = self.interner.function_modifiers(&func_id).visibility;
55+
if !trait_member_is_visible(trait_id, visibility, self.module_id, self.def_maps) {
56+
continue;
57+
}
58+
59+
let trait_ = self.interner.get_trait(trait_id);
60+
self.push_import_trait_code_action(trait_);
61+
}
62+
}
63+
64+
fn push_import_trait_code_action(&mut self, trait_: &Trait) {
65+
let trait_id = trait_.id;
66+
67+
let module_def_id = ModuleDefId::TraitId(trait_id);
68+
let current_module_parent_id = self.module_id.parent(self.def_maps);
69+
let Some(module_full_path) = relative_module_full_path(
70+
module_def_id,
71+
self.module_id,
72+
current_module_parent_id,
73+
self.interner,
74+
) else {
75+
return;
76+
};
77+
let full_path = format!("{}::{}", module_full_path, trait_.name);
78+
79+
let title = format!("Import {}", full_path);
80+
81+
let text_edits = use_completion_item_additional_text_edits(
82+
UseCompletionItemAdditionTextEditsRequest {
83+
full_path: &full_path,
84+
files: self.files,
85+
file: self.file,
86+
lines: &self.lines,
87+
nesting: self.nesting,
88+
auto_import_line: self.auto_import_line,
89+
},
90+
&self.use_segment_positions,
91+
);
92+
93+
let code_action = self.new_quick_fix_multiple_edits(title, text_edits);
94+
self.code_actions.push(code_action);
95+
}
96+
}
97+
98+
#[cfg(test)]
99+
mod tests {
100+
use tokio::test;
101+
102+
use crate::requests::code_action::tests::assert_code_action;
103+
104+
#[test]
105+
async fn test_import_trait_in_method_call_when_one_option_but_not_in_scope() {
106+
let title = "Import moo::Foo";
107+
108+
let src = r#"mod moo {
109+
pub trait Foo {
110+
fn foobar(self);
111+
}
112+
113+
impl Foo for Field {
114+
fn foobar(self) {}
115+
}
116+
}
117+
118+
fn main() {
119+
let x: Field = 1;
120+
x.foo>|<bar();
121+
}"#;
122+
123+
let expected = r#"use moo::Foo;
124+
125+
mod moo {
126+
pub trait Foo {
127+
fn foobar(self);
128+
}
129+
130+
impl Foo for Field {
131+
fn foobar(self) {}
132+
}
133+
}
134+
135+
fn main() {
136+
let x: Field = 1;
137+
x.foobar();
138+
}"#;
139+
140+
assert_code_action(title, src, expected).await;
141+
}
142+
143+
#[test]
144+
async fn test_import_trait_in_method_call_when_multiple_options_1() {
145+
let title = "Import moo::Foo";
146+
147+
let src = r#"mod moo {
148+
pub trait Foo {
149+
fn foobar(self);
150+
}
151+
152+
impl Foo for Field {
153+
fn foobar(self) {}
154+
}
155+
156+
pub trait Bar {
157+
fn foobar(self);
158+
}
159+
160+
impl Bar for Field {
161+
fn foobar(self) {}
162+
}
163+
}
164+
165+
fn main() {
166+
let x: Field = 1;
167+
x.foo>|<bar();
168+
}"#;
169+
170+
let expected = r#"use moo::Foo;
171+
172+
mod moo {
173+
pub trait Foo {
174+
fn foobar(self);
175+
}
176+
177+
impl Foo for Field {
178+
fn foobar(self) {}
179+
}
180+
181+
pub trait Bar {
182+
fn foobar(self);
183+
}
184+
185+
impl Bar for Field {
186+
fn foobar(self) {}
187+
}
188+
}
189+
190+
fn main() {
191+
let x: Field = 1;
192+
x.foobar();
193+
}"#;
194+
195+
assert_code_action(title, src, expected).await;
196+
}
197+
198+
#[test]
199+
async fn test_import_trait_in_method_call_when_multiple_options_2() {
200+
let title = "Import moo::Bar";
201+
202+
let src = r#"mod moo {
203+
pub trait Foo {
204+
fn foobar(self);
205+
}
206+
207+
impl Foo for Field {
208+
fn foobar(self) {}
209+
}
210+
211+
pub trait Bar {
212+
fn foobar(self);
213+
}
214+
215+
impl Bar for Field {
216+
fn foobar(self) {}
217+
}
218+
}
219+
220+
fn main() {
221+
let x: Field = 1;
222+
x.foo>|<bar();
223+
}"#;
224+
225+
let expected = r#"use moo::Bar;
226+
227+
mod moo {
228+
pub trait Foo {
229+
fn foobar(self);
230+
}
231+
232+
impl Foo for Field {
233+
fn foobar(self) {}
234+
}
235+
236+
pub trait Bar {
237+
fn foobar(self);
238+
}
239+
240+
impl Bar for Field {
241+
fn foobar(self) {}
242+
}
243+
}
244+
245+
fn main() {
246+
let x: Field = 1;
247+
x.foobar();
248+
}"#;
249+
250+
assert_code_action(title, src, expected).await;
251+
}
252+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ async fn get_code_action(src: &str) -> CodeActionResponse {
4747
)
4848
.await
4949
.expect("Could not execute on_code_action_request")
50-
.unwrap()
50+
.expect("Expected to get a CodeActionResponse, got None")
5151
}
5252

5353
pub(crate) async fn assert_code_action(title: &str, src: &str, expected: &str) {

0 commit comments

Comments
 (0)