Skip to content

Commit 8b3c851

Browse files
bors[bot]adamrk
andauthored
Merge #6098
6098: Insert ref for completions r=adamrk a=adamrk Follow up to #5846. When we have a local in scope which needs a ref or mutable ref to match the name and type of the active in the completion context then a new completion item with `&` or `&mut ` is inserted. E.g. ```rust fn foo(arg: &i32){}; fn main() { let arg = 1_i32; foo(a<|>) } ``` now offers `&arg` as a completion option with the highest score. Co-authored-by: adamrk <[email protected]>
2 parents edf46a1 + 3dbbcfc commit 8b3c851

File tree

5 files changed

+132
-26
lines changed

5 files changed

+132
-26
lines changed

crates/completion/src/completion_context.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,19 @@ impl<'a> CompletionContext<'a> {
246246
}
247247
}
248248

249+
pub(crate) fn active_name_and_type(&self) -> Option<(String, Type)> {
250+
if let Some(record_field) = &self.record_field_syntax {
251+
mark::hit!(record_field_type_match);
252+
let (struct_field, _local) = self.sema.resolve_record_field(record_field)?;
253+
Some((struct_field.name(self.db).to_string(), struct_field.signature_ty(self.db)))
254+
} else if let Some(active_parameter) = &self.active_parameter {
255+
mark::hit!(active_param_type_match);
256+
Some((active_parameter.name.clone(), active_parameter.ty.clone()))
257+
} else {
258+
None
259+
}
260+
}
261+
249262
fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
250263
let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
251264
let syntax_element = NodeOrToken::Token(fake_ident_token);

crates/completion/src/completion_item.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::fmt;
44

5-
use hir::Documentation;
5+
use hir::{Documentation, Mutability};
66
use syntax::TextRange;
77
use text_edit::TextEdit;
88

@@ -56,6 +56,10 @@ pub struct CompletionItem {
5656

5757
/// Score is useful to pre select or display in better order completion items
5858
score: Option<CompletionScore>,
59+
60+
/// Indicates that a reference or mutable reference to this variable is a
61+
/// possible match.
62+
ref_match: Option<(Mutability, CompletionScore)>,
5963
}
6064

6165
// We use custom debug for CompletionItem to make snapshot tests more readable.
@@ -194,6 +198,7 @@ impl CompletionItem {
194198
deprecated: None,
195199
trigger_call_info: None,
196200
score: None,
201+
ref_match: None,
197202
}
198203
}
199204
/// What user sees in pop-up in the UI.
@@ -240,10 +245,15 @@ impl CompletionItem {
240245
pub fn trigger_call_info(&self) -> bool {
241246
self.trigger_call_info
242247
}
248+
249+
pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> {
250+
self.ref_match
251+
}
243252
}
244253

245254
/// A helper to make `CompletionItem`s.
246255
#[must_use]
256+
#[derive(Clone)]
247257
pub(crate) struct Builder {
248258
source_range: TextRange,
249259
completion_kind: CompletionKind,
@@ -258,6 +268,7 @@ pub(crate) struct Builder {
258268
deprecated: Option<bool>,
259269
trigger_call_info: Option<bool>,
260270
score: Option<CompletionScore>,
271+
ref_match: Option<(Mutability, CompletionScore)>,
261272
}
262273

263274
impl Builder {
@@ -288,6 +299,7 @@ impl Builder {
288299
deprecated: self.deprecated.unwrap_or(false),
289300
trigger_call_info: self.trigger_call_info.unwrap_or(false),
290301
score: self.score,
302+
ref_match: self.ref_match,
291303
}
292304
}
293305
pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
@@ -350,6 +362,13 @@ impl Builder {
350362
self.trigger_call_info = Some(true);
351363
self
352364
}
365+
pub(crate) fn set_ref_match(
366+
mut self,
367+
ref_match: Option<(Mutability, CompletionScore)>,
368+
) -> Builder {
369+
self.ref_match = ref_match;
370+
self
371+
}
353372
}
354373

355374
impl<'a> Into<CompletionItem> for Builder {

crates/completion/src/presentation.rs

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! This modules takes care of rendering various definitions as completion items.
22
//! It also handles scoring (sorting) completions.
33
4-
use hir::{HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type};
4+
use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type};
55
use itertools::Itertools;
66
use syntax::{ast::NameOwner, display::*};
77
use test_utils::mark;
@@ -107,9 +107,16 @@ impl Completions {
107107
}
108108
};
109109

110+
let mut ref_match = None;
110111
if let ScopeDef::Local(local) = resolution {
111-
if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) {
112-
completion_item = completion_item.set_score(score);
112+
if let Some((active_name, active_type)) = ctx.active_name_and_type() {
113+
let ty = local.ty(ctx.db);
114+
if let Some(score) =
115+
compute_score_from_active(&active_type, &active_name, &ty, &local_name)
116+
{
117+
completion_item = completion_item.set_score(score);
118+
}
119+
ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name);
113120
}
114121
}
115122

@@ -131,7 +138,7 @@ impl Completions {
131138
}
132139
}
133140

134-
completion_item.kind(kind).set_documentation(docs).add_to(self)
141+
completion_item.kind(kind).set_documentation(docs).set_ref_match(ref_match).add_to(self)
135142
}
136143

137144
pub(crate) fn add_macro(
@@ -342,25 +349,15 @@ impl Completions {
342349
}
343350
}
344351

345-
pub(crate) fn compute_score(
346-
ctx: &CompletionContext,
352+
fn compute_score_from_active(
353+
active_type: &Type,
354+
active_name: &str,
347355
ty: &Type,
348356
name: &str,
349357
) -> Option<CompletionScore> {
350-
let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax {
351-
mark::hit!(record_field_type_match);
352-
let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?;
353-
(struct_field.name(ctx.db).to_string(), struct_field.signature_ty(ctx.db))
354-
} else if let Some(active_parameter) = &ctx.active_parameter {
355-
mark::hit!(active_param_type_match);
356-
(active_parameter.name.clone(), active_parameter.ty.clone())
357-
} else {
358-
return None;
359-
};
360-
361358
// Compute score
362359
// For the same type
363-
if &active_type != ty {
360+
if active_type != ty {
364361
return None;
365362
}
366363

@@ -373,6 +370,24 @@ pub(crate) fn compute_score(
373370

374371
Some(res)
375372
}
373+
fn refed_type_matches(
374+
active_type: &Type,
375+
active_name: &str,
376+
ty: &Type,
377+
name: &str,
378+
) -> Option<(Mutability, CompletionScore)> {
379+
let derefed_active = active_type.remove_ref()?;
380+
let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?;
381+
Some((
382+
if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared },
383+
score,
384+
))
385+
}
386+
387+
fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option<CompletionScore> {
388+
let (active_name, active_type) = ctx.active_name_and_type()?;
389+
compute_score_from_active(&active_type, &active_name, ty, name)
390+
}
376391

377392
enum Params {
378393
Named(Vec<String>),

crates/rust-analyzer/src/handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ pub(crate) fn handle_completion(
570570
let line_endings = snap.file_line_endings(position.file_id);
571571
let items: Vec<CompletionItem> = items
572572
.into_iter()
573-
.map(|item| to_proto::completion_item(&line_index, line_endings, item))
573+
.flat_map(|item| to_proto::completion_item(&line_index, line_endings, item))
574574
.collect();
575575

576576
Ok(Some(items.into()))

crates/rust-analyzer/src/to_proto.rs

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,13 @@ pub(crate) fn completion_item(
160160
line_index: &LineIndex,
161161
line_endings: LineEndings,
162162
completion_item: CompletionItem,
163-
) -> lsp_types::CompletionItem {
163+
) -> Vec<lsp_types::CompletionItem> {
164+
fn set_score(res: &mut lsp_types::CompletionItem, label: &str) {
165+
res.preselect = Some(true);
166+
// HACK: sort preselect items first
167+
res.sort_text = Some(format!(" {}", label));
168+
}
169+
164170
let mut additional_text_edits = Vec::new();
165171
let mut text_edit = None;
166172
// LSP does not allow arbitrary edits in completion, so we have to do a
@@ -200,9 +206,7 @@ pub(crate) fn completion_item(
200206
};
201207

202208
if completion_item.score().is_some() {
203-
res.preselect = Some(true);
204-
// HACK: sort preselect items first
205-
res.sort_text = Some(format!(" {}", completion_item.label()));
209+
set_score(&mut res, completion_item.label());
206210
}
207211

208212
if completion_item.deprecated() {
@@ -217,9 +221,22 @@ pub(crate) fn completion_item(
217221
});
218222
}
219223

220-
res.insert_text_format = Some(insert_text_format(completion_item.insert_text_format()));
224+
let mut all_results = match completion_item.ref_match() {
225+
Some(ref_match) => {
226+
let mut refed = res.clone();
227+
let (mutability, _score) = ref_match;
228+
let label = format!("&{}{}", mutability.as_keyword_for_ref(), refed.label);
229+
set_score(&mut refed, &label);
230+
refed.label = label;
231+
vec![res, refed]
232+
}
233+
None => vec![res],
234+
};
221235

222-
res
236+
for mut r in all_results.iter_mut() {
237+
r.insert_text_format = Some(insert_text_format(completion_item.insert_text_format()));
238+
}
239+
all_results
223240
}
224241

225242
pub(crate) fn signature_help(
@@ -775,6 +792,48 @@ mod tests {
775792

776793
use super::*;
777794

795+
#[test]
796+
fn test_completion_with_ref() {
797+
let fixture = r#"
798+
struct Foo;
799+
fn foo(arg: &Foo) {}
800+
fn main() {
801+
let arg = Foo;
802+
foo(<|>)
803+
}"#;
804+
805+
let (offset, text) = test_utils::extract_offset(fixture);
806+
let line_index = LineIndex::new(&text);
807+
let (analysis, file_id) = Analysis::from_single_file(text);
808+
let completions: Vec<(String, Option<String>)> = analysis
809+
.completions(
810+
&ide::CompletionConfig::default(),
811+
base_db::FilePosition { file_id, offset },
812+
)
813+
.unwrap()
814+
.unwrap()
815+
.into_iter()
816+
.filter(|c| c.label().ends_with("arg"))
817+
.map(|c| completion_item(&line_index, LineEndings::Unix, c))
818+
.flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
819+
.collect();
820+
expect_test::expect![[r#"
821+
[
822+
(
823+
"arg",
824+
None,
825+
),
826+
(
827+
"&arg",
828+
Some(
829+
" &arg",
830+
),
831+
),
832+
]
833+
"#]]
834+
.assert_debug_eq(&completions);
835+
}
836+
778837
#[test]
779838
fn conv_fold_line_folding_only_fixup() {
780839
let text = r#"mod a;

0 commit comments

Comments
 (0)