diff --git a/crates/hir-ty/src/chalk_ext.rs b/crates/hir-ty/src/chalk_ext.rs index b0885ab003f7..a9c124b42dc2 100644 --- a/crates/hir-ty/src/chalk_ext.rs +++ b/crates/hir-ty/src/chalk_ext.rs @@ -34,6 +34,7 @@ pub trait TyExt { fn callable_sig(&self, db: &dyn HirDatabase) -> Option; fn strip_references(&self) -> &Ty; + fn strip_reference(&self) -> &Ty; /// If this is a `dyn Trait`, returns that trait. fn dyn_trait(&self) -> Option; @@ -182,6 +183,10 @@ impl TyExt for Ty { t } + fn strip_reference(&self) -> &Ty { + self.as_reference().map_or(self, |(ty, _, _)| ty) + } + fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option> { match self.kind(Interner) { TyKind::OpaqueType(opaque_ty_id, subst) => { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index d4925455d7bd..8f984210e117 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2769,6 +2769,10 @@ impl Type { self.derived(self.ty.strip_references().clone()) } + pub fn strip_reference(&self) -> Type { + self.derived(self.ty.strip_reference().clone()) + } + pub fn is_unknown(&self) -> bool { self.ty.is_unknown() } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index c71ffa0ed86f..09a1a99eb64c 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -162,11 +162,52 @@ impl<'a> CompletionContext<'a> { } /// Calculate the expected type and name of the cursor position. - fn expected_type_and_name(&self) -> (Option, Option) { + fn expected_type_and_name( + &self, + name_like: &ast::NameLike, + ) -> (Option, Option) { let mut node = match self.token.parent() { Some(it) => it, None => return (None, None), }; + + let strip_refs = |mut ty: Type| match name_like { + ast::NameLike::NameRef(n) => { + let p = match n.syntax().parent() { + Some(it) => it, + None => return ty, + }; + let top_syn = match_ast! { + match p { + ast::FieldExpr(e) => e + .syntax() + .ancestors() + .map_while(ast::FieldExpr::cast) + .last() + .map(|it| it.syntax().clone()), + ast::PathSegment(e) => e + .syntax() + .ancestors() + .skip(1) + .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind())) + .find_map(ast::PathExpr::cast) + .map(|it| it.syntax().clone()), + _ => None + } + }; + let top_syn = match top_syn { + Some(it) => it, + None => return ty, + }; + for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) { + cov_mark::hit!(expected_type_fn_param_ref); + ty = ty.strip_reference(); + } + ty + } + _ => ty, + }; + loop { break match_ast! { match node { @@ -199,13 +240,9 @@ impl<'a> CompletionContext<'a> { self.token.clone(), ).map(|ap| { let name = ap.ident().map(NameOrNameRef::Name); - let ty = if has_ref(&self.token) { - cov_mark::hit!(expected_type_fn_param_ref); - ap.ty.remove_ref() - } else { - Some(ap.ty) - }; - (ty, name) + + let ty = strip_refs(ap.ty); + (Some(ty), name) }) .unwrap_or((None, None)) }, @@ -330,8 +367,6 @@ impl<'a> CompletionContext<'a> { return None; } - (self.expected_type, self.expected_name) = self.expected_type_and_name(); - // Overwrite the path kind for derives if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx { if let Some(ast::NameLike::NameRef(name_ref)) = @@ -389,6 +424,7 @@ impl<'a> CompletionContext<'a> { return Some(analysis); } }; + (self.expected_type, self.expected_name) = self.expected_type_and_name(&name_like); let analysis = match name_like { ast::NameLike::Lifetime(lifetime) => CompletionAnalysis::Lifetime( Self::classify_lifetime(&self.sema, original_file, lifetime)?, @@ -1141,19 +1177,6 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> { Some((use_tree.path()?, true)) } -fn has_ref(token: &SyntaxToken) -> bool { - let mut token = token.clone(); - for skip in [SyntaxKind::IDENT, SyntaxKind::WHITESPACE, T![mut]] { - if token.kind() == skip { - token = match token.prev_token() { - Some(it) => it, - None => return false, - } - } - } - token.kind() == T![&] -} - pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool { // oh my ... (|| { diff --git a/crates/ide-completion/src/context/tests.rs b/crates/ide-completion/src/context/tests.rs index c5557bdafb33..50845b3881f4 100644 --- a/crates/ide-completion/src/context/tests.rs +++ b/crates/ide-completion/src/context/tests.rs @@ -391,3 +391,23 @@ fn foo($0: Foo) {} expect![[r#"ty: ?, name: ?"#]], ); } + +#[test] +fn expected_type_ref_prefix_on_field() { + check_expected_type_and_name( + r#" +fn foo(_: &mut i32) {} +struct S { + field: i32, +} + +fn main() { + let s = S { + field: 100, + }; + foo(&mut s.f$0); +} +"#, + expect!["ty: i32, name: ?"], + ); +}