mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-10-31 12:04:43 +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
 | ||||
|     // 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
 | ||||
|     pub ref_match: Option<(Mutability, TextSize)>, | ||||
|     pub ref_match: Option<(CompletionItemRefMode, TextSize)>, | ||||
| 
 | ||||
|     /// The import data to add to completion's edits.
 | ||||
|     /// (ImportPath, LastSegment)
 | ||||
|  | @ -128,8 +128,15 @@ impl fmt::Debug for CompletionItem { | |||
|             s.field("relevance", &self.relevance); | ||||
|         } | ||||
| 
 | ||||
|         if let Some((mutability, offset)) = &self.ref_match { | ||||
|             s.field("ref_match", &format!("&{}@{offset:?}", mutability.as_keyword_for_ref())); | ||||
|         if let Some((ref_mode, offset)) = self.ref_match { | ||||
|             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 { | ||||
|             s.field("trigger_call_info", &true); | ||||
|  | @ -400,6 +407,12 @@ impl CompletionItemKind { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Copy, Clone, Debug)] | ||||
| pub enum CompletionItemRefMode { | ||||
|     Reference(Mutability), | ||||
|     Dereference, | ||||
| } | ||||
| 
 | ||||
| impl CompletionItem { | ||||
|     pub(crate) fn new( | ||||
|         kind: impl Into<CompletionItemKind>, | ||||
|  | @ -441,15 +454,14 @@ impl CompletionItem { | |||
|         let mut relevance = self.relevance; | ||||
|         relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact); | ||||
| 
 | ||||
|         self.ref_match.map(|(mutability, offset)| { | ||||
|             ( | ||||
|                 format!("&{}{}", mutability.as_keyword_for_ref(), self.label.primary), | ||||
|                 ide_db::text_edit::Indel::insert( | ||||
|                     offset, | ||||
|                     format!("&{}", mutability.as_keyword_for_ref()), | ||||
|                 ), | ||||
|                 relevance, | ||||
|             ) | ||||
|         self.ref_match.map(|(mode, offset)| { | ||||
|             let prefix = match mode { | ||||
|                 CompletionItemRefMode::Reference(Mutability::Shared) => "&", | ||||
|                 CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ", | ||||
|                 CompletionItemRefMode::Dereference => "*", | ||||
|             }; | ||||
|             let label = format!("{prefix}{}", self.label.primary); | ||||
|             (label, ide_db::text_edit::Indel::insert(offset, String::from(prefix)), relevance) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | @ -473,7 +485,7 @@ pub(crate) struct Builder { | |||
|     deprecated: bool, | ||||
|     trigger_call_info: bool, | ||||
|     relevance: CompletionRelevance, | ||||
|     ref_match: Option<(Mutability, TextSize)>, | ||||
|     ref_match: Option<(CompletionItemRefMode, TextSize)>, | ||||
|     edition: Edition, | ||||
| } | ||||
| 
 | ||||
|  | @ -657,8 +669,12 @@ impl Builder { | |||
|         self.imports_to_add.push(import_to_add); | ||||
|         self | ||||
|     } | ||||
|     pub(crate) fn ref_match(&mut self, mutability: Mutability, offset: TextSize) -> &mut Builder { | ||||
|         self.ref_match = Some((mutability, offset)); | ||||
|     pub(crate) fn ref_match( | ||||
|         &mut self, | ||||
|         ref_mode: CompletionItemRefMode, | ||||
|         offset: TextSize, | ||||
|     ) -> &mut Builder { | ||||
|         self.ref_match = Some((ref_mode, offset)); | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -33,8 +33,9 @@ use crate::{ | |||
| pub use crate::{ | ||||
|     config::{AutoImportExclusionType, CallableSnippets, CompletionConfig}, | ||||
|     item::{ | ||||
|         CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch, | ||||
|         CompletionRelevanceReturnType, CompletionRelevanceTypeMatch, | ||||
|         CompletionItem, CompletionItemKind, CompletionItemRefMode, CompletionRelevance, | ||||
|         CompletionRelevancePostfixMatch, CompletionRelevanceReturnType, | ||||
|         CompletionRelevanceTypeMatch, | ||||
|     }, | ||||
|     snippet::{Snippet, SnippetScope}, | ||||
| }; | ||||
|  |  | |||
|  | @ -28,7 +28,8 @@ use crate::{ | |||
|         literal::render_variant_lit, | ||||
|         macro_::{render_macro, render_macro_pat}, | ||||
|     }, | ||||
|     CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, | ||||
|     CompletionContext, CompletionItem, CompletionItemKind, CompletionItemRefMode, | ||||
|     CompletionRelevance, | ||||
| }; | ||||
| /// Interface for data and methods required for items rendering.
 | ||||
| #[derive(Debug, Clone)] | ||||
|  | @ -192,8 +193,8 @@ pub(crate) fn render_field( | |||
|     } | ||||
|     if let Some(receiver) = &dot_access.receiver { | ||||
|         if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) { | ||||
|             if let Some(ref_match) = compute_ref_match(ctx.completion, ty) { | ||||
|                 item.ref_match(ref_match, original.syntax().text_range().start()); | ||||
|             if let Some(ref_mode) = compute_ref_match(ctx.completion, ty) { | ||||
|                 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( | ||||
|     ctx: &CompletionContext<'_>, | ||||
|     completion_ty: &hir::Type, | ||||
| ) -> Option<hir::Mutability> { | ||||
| ) -> Option<CompletionItemRefMode> { | ||||
|     let expected_type = ctx.expected_type.as_ref()?; | ||||
|     if completion_ty != expected_type { | ||||
|         let expected_type_without_ref = expected_type.remove_ref()?; | ||||
|         if completion_ty.autoderef(ctx.db).any(|deref_ty| deref_ty == expected_type_without_ref) { | ||||
|     let expected_without_ref = expected_type.remove_ref(); | ||||
|     let completion_without_ref = completion_ty.remove_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); | ||||
|             let mutability = if expected_type.is_mutable_reference() { | ||||
|                 hir::Mutability::Mut | ||||
|             } else { | ||||
|                 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 | ||||
| } | ||||
| 
 | ||||
|  | @ -664,16 +679,16 @@ fn path_ref_match( | |||
|     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
 | ||||
|         if let Some(original_path) = completion.sema.original_ast_node(original_path.clone()) { | ||||
|             if let Some(ref_match) = compute_ref_match(completion, ty) { | ||||
|                 item.ref_match(ref_match, original_path.syntax().text_range().start()); | ||||
|             if let Some(ref_mode) = compute_ref_match(completion, ty) { | ||||
|                 item.ref_match(ref_mode, original_path.syntax().text_range().start()); | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         // 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
 | ||||
|         // as long as nothing was typed yet
 | ||||
|         if let Some(ref_match) = compute_ref_match(completion, ty) { | ||||
|             item.ref_match(ref_match, completion.position.offset); | ||||
|         if let Some(ref_mode) = compute_ref_match(completion, ty) { | ||||
|             item.ref_match(ref_mode, completion.position.offset); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -2065,7 +2080,42 @@ fn main() { | |||
|     } | ||||
| 
 | ||||
|     #[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( | ||||
|             r#" | ||||
| //- minicore: deref
 | ||||
|  |  | |||
|  | @ -143,8 +143,8 @@ fn render( | |||
|         } | ||||
|         FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => { | ||||
|             if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) { | ||||
|                 if let Some(ref_match) = compute_ref_match(completion, &ret_type) { | ||||
|                     item.ref_match(ref_match, original_expr.syntax().text_range().start()); | ||||
|                 if let Some(ref_mode) = compute_ref_match(completion, &ret_type) { | ||||
|                     item.ref_match(ref_mode, original_expr.syntax().text_range().start()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -120,7 +120,7 @@ pub use ide_assists::{ | |||
| }; | ||||
| pub use ide_completion::{ | ||||
|     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::{ | ||||
|  |  | |||
|  | @ -47,7 +47,8 @@ use self::lsp::ext as lsp_ext; | |||
| #[cfg(test)] | ||||
| mod integrated_benchmarks; | ||||
| 
 | ||||
| use ide::{CompletionItem, CompletionRelevance}; | ||||
| use hir::Mutability; | ||||
| use ide::{CompletionItem, CompletionItemRefMode, CompletionRelevance}; | ||||
| use serde::de::DeserializeOwned; | ||||
| use tenthash::TentHasher; | ||||
| 
 | ||||
|  | @ -132,8 +133,13 @@ fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8; | |||
|         hasher.update(detail); | ||||
|     } | ||||
|     hash_completion_relevance(&mut hasher, &item.relevance); | ||||
|     if let Some((mutability, text_size)) = &item.ref_match { | ||||
|         hasher.update(mutability.as_keyword_for_ref()); | ||||
|     if let Some((ref_mode, text_size)) = &item.ref_match { | ||||
|         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()); | ||||
|     } | ||||
|     for (import_path, import_name) in &item.import_to_add { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lukas Wirth
						Lukas Wirth