mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-28 10:39:45 +00:00
Merge pull request #18917 from boattime/master
feat: Add dereferencing autocomplete
This commit is contained in:
commit
d82e1a2472
6 changed files with 110 additions and 37 deletions
|
|
@ -79,7 +79,7 @@ pub struct CompletionItem {
|
||||||
// FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though
|
// FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though
|
||||||
// until we have more splitting completions in which case we should think about
|
// until we have more splitting completions in which case we should think about
|
||||||
// generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571
|
// generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571
|
||||||
pub ref_match: Option<(Mutability, TextSize)>,
|
pub ref_match: Option<(CompletionItemRefMode, TextSize)>,
|
||||||
|
|
||||||
/// The import data to add to completion's edits.
|
/// The import data to add to completion's edits.
|
||||||
/// (ImportPath, LastSegment)
|
/// (ImportPath, LastSegment)
|
||||||
|
|
@ -128,8 +128,15 @@ impl fmt::Debug for CompletionItem {
|
||||||
s.field("relevance", &self.relevance);
|
s.field("relevance", &self.relevance);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((mutability, offset)) = &self.ref_match {
|
if let Some((ref_mode, offset)) = self.ref_match {
|
||||||
s.field("ref_match", &format!("&{}@{offset:?}", mutability.as_keyword_for_ref()));
|
let prefix = match ref_mode {
|
||||||
|
CompletionItemRefMode::Reference(mutability) => match mutability {
|
||||||
|
Mutability::Shared => "&",
|
||||||
|
Mutability::Mut => "&mut ",
|
||||||
|
},
|
||||||
|
CompletionItemRefMode::Dereference => "*",
|
||||||
|
};
|
||||||
|
s.field("ref_match", &format!("{}@{offset:?}", prefix));
|
||||||
}
|
}
|
||||||
if self.trigger_call_info {
|
if self.trigger_call_info {
|
||||||
s.field("trigger_call_info", &true);
|
s.field("trigger_call_info", &true);
|
||||||
|
|
@ -400,6 +407,12 @@ impl CompletionItemKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum CompletionItemRefMode {
|
||||||
|
Reference(Mutability),
|
||||||
|
Dereference,
|
||||||
|
}
|
||||||
|
|
||||||
impl CompletionItem {
|
impl CompletionItem {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
kind: impl Into<CompletionItemKind>,
|
kind: impl Into<CompletionItemKind>,
|
||||||
|
|
@ -441,15 +454,14 @@ impl CompletionItem {
|
||||||
let mut relevance = self.relevance;
|
let mut relevance = self.relevance;
|
||||||
relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
|
relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
|
||||||
|
|
||||||
self.ref_match.map(|(mutability, offset)| {
|
self.ref_match.map(|(mode, offset)| {
|
||||||
(
|
let prefix = match mode {
|
||||||
format!("&{}{}", mutability.as_keyword_for_ref(), self.label.primary),
|
CompletionItemRefMode::Reference(Mutability::Shared) => "&",
|
||||||
ide_db::text_edit::Indel::insert(
|
CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ",
|
||||||
offset,
|
CompletionItemRefMode::Dereference => "*",
|
||||||
format!("&{}", mutability.as_keyword_for_ref()),
|
};
|
||||||
),
|
let label = format!("{prefix}{}", self.label.primary);
|
||||||
relevance,
|
(label, ide_db::text_edit::Indel::insert(offset, String::from(prefix)), relevance)
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -473,7 +485,7 @@ pub(crate) struct Builder {
|
||||||
deprecated: bool,
|
deprecated: bool,
|
||||||
trigger_call_info: bool,
|
trigger_call_info: bool,
|
||||||
relevance: CompletionRelevance,
|
relevance: CompletionRelevance,
|
||||||
ref_match: Option<(Mutability, TextSize)>,
|
ref_match: Option<(CompletionItemRefMode, TextSize)>,
|
||||||
edition: Edition,
|
edition: Edition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -657,8 +669,12 @@ impl Builder {
|
||||||
self.imports_to_add.push(import_to_add);
|
self.imports_to_add.push(import_to_add);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub(crate) fn ref_match(&mut self, mutability: Mutability, offset: TextSize) -> &mut Builder {
|
pub(crate) fn ref_match(
|
||||||
self.ref_match = Some((mutability, offset));
|
&mut self,
|
||||||
|
ref_mode: CompletionItemRefMode,
|
||||||
|
offset: TextSize,
|
||||||
|
) -> &mut Builder {
|
||||||
|
self.ref_match = Some((ref_mode, offset));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,9 @@ use crate::{
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
config::{AutoImportExclusionType, CallableSnippets, CompletionConfig},
|
config::{AutoImportExclusionType, CallableSnippets, CompletionConfig},
|
||||||
item::{
|
item::{
|
||||||
CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
|
CompletionItem, CompletionItemKind, CompletionItemRefMode, CompletionRelevance,
|
||||||
CompletionRelevanceReturnType, CompletionRelevanceTypeMatch,
|
CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
|
||||||
|
CompletionRelevanceTypeMatch,
|
||||||
},
|
},
|
||||||
snippet::{Snippet, SnippetScope},
|
snippet::{Snippet, SnippetScope},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ use crate::{
|
||||||
literal::render_variant_lit,
|
literal::render_variant_lit,
|
||||||
macro_::{render_macro, render_macro_pat},
|
macro_::{render_macro, render_macro_pat},
|
||||||
},
|
},
|
||||||
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
|
CompletionContext, CompletionItem, CompletionItemKind, CompletionItemRefMode,
|
||||||
|
CompletionRelevance,
|
||||||
};
|
};
|
||||||
/// Interface for data and methods required for items rendering.
|
/// Interface for data and methods required for items rendering.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -192,8 +193,8 @@ pub(crate) fn render_field(
|
||||||
}
|
}
|
||||||
if let Some(receiver) = &dot_access.receiver {
|
if let Some(receiver) = &dot_access.receiver {
|
||||||
if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
|
if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
|
||||||
if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {
|
if let Some(ref_mode) = compute_ref_match(ctx.completion, ty) {
|
||||||
item.ref_match(ref_match, original.syntax().text_range().start());
|
item.ref_match(ref_mode, original.syntax().text_range().start());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -638,20 +639,34 @@ fn compute_exact_name_match(ctx: &CompletionContext<'_>, completion_name: &str)
|
||||||
fn compute_ref_match(
|
fn compute_ref_match(
|
||||||
ctx: &CompletionContext<'_>,
|
ctx: &CompletionContext<'_>,
|
||||||
completion_ty: &hir::Type,
|
completion_ty: &hir::Type,
|
||||||
) -> Option<hir::Mutability> {
|
) -> Option<CompletionItemRefMode> {
|
||||||
let expected_type = ctx.expected_type.as_ref()?;
|
let expected_type = ctx.expected_type.as_ref()?;
|
||||||
if completion_ty != expected_type {
|
let expected_without_ref = expected_type.remove_ref();
|
||||||
let expected_type_without_ref = expected_type.remove_ref()?;
|
let completion_without_ref = completion_ty.remove_ref();
|
||||||
if completion_ty.autoderef(ctx.db).any(|deref_ty| deref_ty == expected_type_without_ref) {
|
|
||||||
|
if completion_ty == expected_type {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(expected_without_ref) = &expected_without_ref {
|
||||||
|
if completion_ty.autoderef(ctx.db).any(|ty| ty == *expected_without_ref) {
|
||||||
cov_mark::hit!(suggest_ref);
|
cov_mark::hit!(suggest_ref);
|
||||||
let mutability = if expected_type.is_mutable_reference() {
|
let mutability = if expected_type.is_mutable_reference() {
|
||||||
hir::Mutability::Mut
|
hir::Mutability::Mut
|
||||||
} else {
|
} else {
|
||||||
hir::Mutability::Shared
|
hir::Mutability::Shared
|
||||||
};
|
};
|
||||||
return Some(mutability);
|
return Some(CompletionItemRefMode::Reference(mutability));
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(completion_without_ref) = completion_without_ref {
|
||||||
|
if completion_without_ref == *expected_type && completion_without_ref.is_copy(ctx.db) {
|
||||||
|
cov_mark::hit!(suggest_deref);
|
||||||
|
return Some(CompletionItemRefMode::Dereference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -664,16 +679,16 @@ fn path_ref_match(
|
||||||
if let Some(original_path) = &path_ctx.original_path {
|
if let Some(original_path) = &path_ctx.original_path {
|
||||||
// At least one char was typed by the user already, in that case look for the original path
|
// At least one char was typed by the user already, in that case look for the original path
|
||||||
if let Some(original_path) = completion.sema.original_ast_node(original_path.clone()) {
|
if let Some(original_path) = completion.sema.original_ast_node(original_path.clone()) {
|
||||||
if let Some(ref_match) = compute_ref_match(completion, ty) {
|
if let Some(ref_mode) = compute_ref_match(completion, ty) {
|
||||||
item.ref_match(ref_match, original_path.syntax().text_range().start());
|
item.ref_match(ref_mode, original_path.syntax().text_range().start());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// completion requested on an empty identifier, there is no path here yet.
|
// completion requested on an empty identifier, there is no path here yet.
|
||||||
// FIXME: This might create inconsistent completions where we show a ref match in macro inputs
|
// FIXME: This might create inconsistent completions where we show a ref match in macro inputs
|
||||||
// as long as nothing was typed yet
|
// as long as nothing was typed yet
|
||||||
if let Some(ref_match) = compute_ref_match(completion, ty) {
|
if let Some(ref_mode) = compute_ref_match(completion, ty) {
|
||||||
item.ref_match(ref_match, completion.position.offset);
|
item.ref_match(ref_mode, completion.position.offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2065,7 +2080,42 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn suggest_deref() {
|
fn suggest_deref_copy() {
|
||||||
|
cov_mark::check!(suggest_deref);
|
||||||
|
check_relevance(
|
||||||
|
r#"
|
||||||
|
//- minicore: copy
|
||||||
|
struct Foo;
|
||||||
|
|
||||||
|
impl Copy for Foo {}
|
||||||
|
impl Clone for Foo {
|
||||||
|
fn clone(&self) -> Self { *self }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar(x: Foo) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let foo = &Foo;
|
||||||
|
bar($0);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
st Foo Foo [type]
|
||||||
|
st Foo Foo [type]
|
||||||
|
ex Foo [type]
|
||||||
|
lc foo &Foo [local]
|
||||||
|
lc *foo [type+local]
|
||||||
|
fn bar(…) fn(Foo) []
|
||||||
|
fn main() fn() []
|
||||||
|
md core []
|
||||||
|
tt Clone []
|
||||||
|
tt Copy []
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn suggest_deref_trait() {
|
||||||
check_relevance(
|
check_relevance(
|
||||||
r#"
|
r#"
|
||||||
//- minicore: deref
|
//- minicore: deref
|
||||||
|
|
|
||||||
|
|
@ -143,8 +143,8 @@ fn render(
|
||||||
}
|
}
|
||||||
FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
|
FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
|
||||||
if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) {
|
if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) {
|
||||||
if let Some(ref_match) = compute_ref_match(completion, &ret_type) {
|
if let Some(ref_mode) = compute_ref_match(completion, &ret_type) {
|
||||||
item.ref_match(ref_match, original_expr.syntax().text_range().start());
|
item.ref_match(ref_mode, original_expr.syntax().text_range().start());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ pub use ide_assists::{
|
||||||
};
|
};
|
||||||
pub use ide_completion::{
|
pub use ide_completion::{
|
||||||
CallableSnippets, CompletionConfig, CompletionFieldsToResolve, CompletionItem,
|
CallableSnippets, CompletionConfig, CompletionFieldsToResolve, CompletionItem,
|
||||||
CompletionItemKind, CompletionRelevance, Snippet, SnippetScope,
|
CompletionItemKind, CompletionItemRefMode, CompletionRelevance, Snippet, SnippetScope,
|
||||||
};
|
};
|
||||||
pub use ide_db::text_edit::{Indel, TextEdit};
|
pub use ide_db::text_edit::{Indel, TextEdit};
|
||||||
pub use ide_db::{
|
pub use ide_db::{
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@ use self::lsp::ext as lsp_ext;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod integrated_benchmarks;
|
mod integrated_benchmarks;
|
||||||
|
|
||||||
use ide::{CompletionItem, CompletionRelevance};
|
use hir::Mutability;
|
||||||
|
use ide::{CompletionItem, CompletionItemRefMode, CompletionRelevance};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use tenthash::TentHasher;
|
use tenthash::TentHasher;
|
||||||
|
|
||||||
|
|
@ -132,8 +133,13 @@ fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8;
|
||||||
hasher.update(detail);
|
hasher.update(detail);
|
||||||
}
|
}
|
||||||
hash_completion_relevance(&mut hasher, &item.relevance);
|
hash_completion_relevance(&mut hasher, &item.relevance);
|
||||||
if let Some((mutability, text_size)) = &item.ref_match {
|
if let Some((ref_mode, text_size)) = &item.ref_match {
|
||||||
hasher.update(mutability.as_keyword_for_ref());
|
let prefix = match ref_mode {
|
||||||
|
CompletionItemRefMode::Reference(Mutability::Shared) => "&",
|
||||||
|
CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ",
|
||||||
|
CompletionItemRefMode::Dereference => "*",
|
||||||
|
};
|
||||||
|
hasher.update(prefix);
|
||||||
hasher.update(u32::from(*text_size).to_le_bytes());
|
hasher.update(u32::from(*text_size).to_le_bytes());
|
||||||
}
|
}
|
||||||
for (import_path, import_name) in &item.import_to_add {
|
for (import_path, import_name) in &item.import_to_add {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue