Merge pull request #18917 from boattime/master

feat: Add dereferencing autocomplete
This commit is contained in:
Lukas Wirth 2025-01-15 07:46:18 +00:00 committed by GitHub
commit d82e1a2472
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 110 additions and 37 deletions

View file

@ -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
} }
} }

View file

@ -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},
}; };

View file

@ -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

View file

@ -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());
} }
} }
} }

View file

@ -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::{

View file

@ -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 {