Skip to content

Commit d82e1a2

Browse files
authored
Merge pull request #18917 from boattime/master
feat: Add dereferencing autocomplete
2 parents 40710f2 + 4168743 commit d82e1a2

File tree

6 files changed

+110
-37
lines changed

6 files changed

+110
-37
lines changed

crates/ide-completion/src/item.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ pub struct CompletionItem {
7979
// FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though
8080
// until we have more splitting completions in which case we should think about
8181
// generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571
82-
pub ref_match: Option<(Mutability, TextSize)>,
82+
pub ref_match: Option<(CompletionItemRefMode, TextSize)>,
8383

8484
/// The import data to add to completion's edits.
8585
/// (ImportPath, LastSegment)
@@ -128,8 +128,15 @@ impl fmt::Debug for CompletionItem {
128128
s.field("relevance", &self.relevance);
129129
}
130130

131-
if let Some((mutability, offset)) = &self.ref_match {
132-
s.field("ref_match", &format!("&{}@{offset:?}", mutability.as_keyword_for_ref()));
131+
if let Some((ref_mode, offset)) = self.ref_match {
132+
let prefix = match ref_mode {
133+
CompletionItemRefMode::Reference(mutability) => match mutability {
134+
Mutability::Shared => "&",
135+
Mutability::Mut => "&mut ",
136+
},
137+
CompletionItemRefMode::Dereference => "*",
138+
};
139+
s.field("ref_match", &format!("{}@{offset:?}", prefix));
133140
}
134141
if self.trigger_call_info {
135142
s.field("trigger_call_info", &true);
@@ -400,6 +407,12 @@ impl CompletionItemKind {
400407
}
401408
}
402409

410+
#[derive(Copy, Clone, Debug)]
411+
pub enum CompletionItemRefMode {
412+
Reference(Mutability),
413+
Dereference,
414+
}
415+
403416
impl CompletionItem {
404417
pub(crate) fn new(
405418
kind: impl Into<CompletionItemKind>,
@@ -441,15 +454,14 @@ impl CompletionItem {
441454
let mut relevance = self.relevance;
442455
relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
443456

444-
self.ref_match.map(|(mutability, offset)| {
445-
(
446-
format!("&{}{}", mutability.as_keyword_for_ref(), self.label.primary),
447-
ide_db::text_edit::Indel::insert(
448-
offset,
449-
format!("&{}", mutability.as_keyword_for_ref()),
450-
),
451-
relevance,
452-
)
457+
self.ref_match.map(|(mode, offset)| {
458+
let prefix = match mode {
459+
CompletionItemRefMode::Reference(Mutability::Shared) => "&",
460+
CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ",
461+
CompletionItemRefMode::Dereference => "*",
462+
};
463+
let label = format!("{prefix}{}", self.label.primary);
464+
(label, ide_db::text_edit::Indel::insert(offset, String::from(prefix)), relevance)
453465
})
454466
}
455467
}
@@ -473,7 +485,7 @@ pub(crate) struct Builder {
473485
deprecated: bool,
474486
trigger_call_info: bool,
475487
relevance: CompletionRelevance,
476-
ref_match: Option<(Mutability, TextSize)>,
488+
ref_match: Option<(CompletionItemRefMode, TextSize)>,
477489
edition: Edition,
478490
}
479491

@@ -657,8 +669,12 @@ impl Builder {
657669
self.imports_to_add.push(import_to_add);
658670
self
659671
}
660-
pub(crate) fn ref_match(&mut self, mutability: Mutability, offset: TextSize) -> &mut Builder {
661-
self.ref_match = Some((mutability, offset));
672+
pub(crate) fn ref_match(
673+
&mut self,
674+
ref_mode: CompletionItemRefMode,
675+
offset: TextSize,
676+
) -> &mut Builder {
677+
self.ref_match = Some((ref_mode, offset));
662678
self
663679
}
664680
}

crates/ide-completion/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ use crate::{
3333
pub use crate::{
3434
config::{AutoImportExclusionType, CallableSnippets, CompletionConfig},
3535
item::{
36-
CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
37-
CompletionRelevanceReturnType, CompletionRelevanceTypeMatch,
36+
CompletionItem, CompletionItemKind, CompletionItemRefMode, CompletionRelevance,
37+
CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
38+
CompletionRelevanceTypeMatch,
3839
},
3940
snippet::{Snippet, SnippetScope},
4041
};

crates/ide-completion/src/render.rs

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ use crate::{
2828
literal::render_variant_lit,
2929
macro_::{render_macro, render_macro_pat},
3030
},
31-
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
31+
CompletionContext, CompletionItem, CompletionItemKind, CompletionItemRefMode,
32+
CompletionRelevance,
3233
};
3334
/// Interface for data and methods required for items rendering.
3435
#[derive(Debug, Clone)]
@@ -192,8 +193,8 @@ pub(crate) fn render_field(
192193
}
193194
if let Some(receiver) = &dot_access.receiver {
194195
if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
195-
if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {
196-
item.ref_match(ref_match, original.syntax().text_range().start());
196+
if let Some(ref_mode) = compute_ref_match(ctx.completion, ty) {
197+
item.ref_match(ref_mode, original.syntax().text_range().start());
197198
}
198199
}
199200
}
@@ -638,20 +639,34 @@ fn compute_exact_name_match(ctx: &CompletionContext<'_>, completion_name: &str)
638639
fn compute_ref_match(
639640
ctx: &CompletionContext<'_>,
640641
completion_ty: &hir::Type,
641-
) -> Option<hir::Mutability> {
642+
) -> Option<CompletionItemRefMode> {
642643
let expected_type = ctx.expected_type.as_ref()?;
643-
if completion_ty != expected_type {
644-
let expected_type_without_ref = expected_type.remove_ref()?;
645-
if completion_ty.autoderef(ctx.db).any(|deref_ty| deref_ty == expected_type_without_ref) {
644+
let expected_without_ref = expected_type.remove_ref();
645+
let completion_without_ref = completion_ty.remove_ref();
646+
647+
if completion_ty == expected_type {
648+
return None;
649+
}
650+
651+
if let Some(expected_without_ref) = &expected_without_ref {
652+
if completion_ty.autoderef(ctx.db).any(|ty| ty == *expected_without_ref) {
646653
cov_mark::hit!(suggest_ref);
647654
let mutability = if expected_type.is_mutable_reference() {
648655
hir::Mutability::Mut
649656
} else {
650657
hir::Mutability::Shared
651658
};
652-
return Some(mutability);
653-
};
659+
return Some(CompletionItemRefMode::Reference(mutability));
660+
}
661+
}
662+
663+
if let Some(completion_without_ref) = completion_without_ref {
664+
if completion_without_ref == *expected_type && completion_without_ref.is_copy(ctx.db) {
665+
cov_mark::hit!(suggest_deref);
666+
return Some(CompletionItemRefMode::Dereference);
667+
}
654668
}
669+
655670
None
656671
}
657672

@@ -664,16 +679,16 @@ fn path_ref_match(
664679
if let Some(original_path) = &path_ctx.original_path {
665680
// At least one char was typed by the user already, in that case look for the original path
666681
if let Some(original_path) = completion.sema.original_ast_node(original_path.clone()) {
667-
if let Some(ref_match) = compute_ref_match(completion, ty) {
668-
item.ref_match(ref_match, original_path.syntax().text_range().start());
682+
if let Some(ref_mode) = compute_ref_match(completion, ty) {
683+
item.ref_match(ref_mode, original_path.syntax().text_range().start());
669684
}
670685
}
671686
} else {
672687
// completion requested on an empty identifier, there is no path here yet.
673688
// FIXME: This might create inconsistent completions where we show a ref match in macro inputs
674689
// as long as nothing was typed yet
675-
if let Some(ref_match) = compute_ref_match(completion, ty) {
676-
item.ref_match(ref_match, completion.position.offset);
690+
if let Some(ref_mode) = compute_ref_match(completion, ty) {
691+
item.ref_match(ref_mode, completion.position.offset);
677692
}
678693
}
679694
}
@@ -2065,7 +2080,42 @@ fn main() {
20652080
}
20662081

20672082
#[test]
2068-
fn suggest_deref() {
2083+
fn suggest_deref_copy() {
2084+
cov_mark::check!(suggest_deref);
2085+
check_relevance(
2086+
r#"
2087+
//- minicore: copy
2088+
struct Foo;
2089+
2090+
impl Copy for Foo {}
2091+
impl Clone for Foo {
2092+
fn clone(&self) -> Self { *self }
2093+
}
2094+
2095+
fn bar(x: Foo) {}
2096+
2097+
fn main() {
2098+
let foo = &Foo;
2099+
bar($0);
2100+
}
2101+
"#,
2102+
expect![[r#"
2103+
st Foo Foo [type]
2104+
st Foo Foo [type]
2105+
ex Foo [type]
2106+
lc foo &Foo [local]
2107+
lc *foo [type+local]
2108+
fn bar(…) fn(Foo) []
2109+
fn main() fn() []
2110+
md core []
2111+
tt Clone []
2112+
tt Copy []
2113+
"#]],
2114+
);
2115+
}
2116+
2117+
#[test]
2118+
fn suggest_deref_trait() {
20692119
check_relevance(
20702120
r#"
20712121
//- minicore: deref

crates/ide-completion/src/render/function.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ fn render(
143143
}
144144
FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
145145
if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) {
146-
if let Some(ref_match) = compute_ref_match(completion, &ret_type) {
147-
item.ref_match(ref_match, original_expr.syntax().text_range().start());
146+
if let Some(ref_mode) = compute_ref_match(completion, &ret_type) {
147+
item.ref_match(ref_mode, original_expr.syntax().text_range().start());
148148
}
149149
}
150150
}

crates/ide/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ pub use ide_assists::{
120120
};
121121
pub use ide_completion::{
122122
CallableSnippets, CompletionConfig, CompletionFieldsToResolve, CompletionItem,
123-
CompletionItemKind, CompletionRelevance, Snippet, SnippetScope,
123+
CompletionItemKind, CompletionItemRefMode, CompletionRelevance, Snippet, SnippetScope,
124124
};
125125
pub use ide_db::text_edit::{Indel, TextEdit};
126126
pub use ide_db::{

crates/rust-analyzer/src/lib.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ use self::lsp::ext as lsp_ext;
4747
#[cfg(test)]
4848
mod integrated_benchmarks;
4949

50-
use ide::{CompletionItem, CompletionRelevance};
50+
use hir::Mutability;
51+
use ide::{CompletionItem, CompletionItemRefMode, CompletionRelevance};
5152
use serde::de::DeserializeOwned;
5253
use tenthash::TentHasher;
5354

@@ -132,8 +133,13 @@ fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8;
132133
hasher.update(detail);
133134
}
134135
hash_completion_relevance(&mut hasher, &item.relevance);
135-
if let Some((mutability, text_size)) = &item.ref_match {
136-
hasher.update(mutability.as_keyword_for_ref());
136+
if let Some((ref_mode, text_size)) = &item.ref_match {
137+
let prefix = match ref_mode {
138+
CompletionItemRefMode::Reference(Mutability::Shared) => "&",
139+
CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ",
140+
CompletionItemRefMode::Dereference => "*",
141+
};
142+
hasher.update(prefix);
137143
hasher.update(u32::from(*text_size).to_le_bytes());
138144
}
139145
for (import_path, import_name) in &item.import_to_add {

0 commit comments

Comments
 (0)