From 7369e5120d88ca0c75d32a6ca3558b64351a6858 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 18 Jun 2022 09:54:03 +0200 Subject: [PATCH 1/4] Move `CompletionContext::function_def` into `PathKind::Expr` --- crates/ide-completion/src/completions/dot.rs | 37 ++++++----- crates/ide-completion/src/completions/expr.rs | 14 ++-- crates/ide-completion/src/context.rs | 6 +- crates/ide-completion/src/context/analysis.rs | 64 +++++++++++++++---- 4 files changed, 82 insertions(+), 39 deletions(-) diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index a315d616d558..da26c2ad6824 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -46,27 +46,26 @@ pub(crate) fn complete_undotted_self( if !ctx.config.enable_self_on_the_fly { return; } - match path_ctx { - PathCompletionCtx { qualified: Qualified::No, kind: PathKind::Expr { .. }, .. } - if path_ctx.is_trivial_path() && ctx.qualifier_ctx.none() => {} + let self_param = match path_ctx { + PathCompletionCtx { + qualified: Qualified::No, + kind: PathKind::Expr { self_param: Some(self_param), .. }, + .. + } if path_ctx.is_trivial_path() && ctx.qualifier_ctx.none() => self_param, _ => return, - } + }; - if let Some(func) = ctx.function_def.as_ref().and_then(|fn_| ctx.sema.to_def(fn_)) { - if let Some(self_) = func.self_param(ctx.db) { - let ty = self_.ty(ctx.db); - complete_fields( - acc, - ctx, - &ty, - |acc, field, ty| acc.add_field(ctx, Some(hir::known::SELF_PARAM), field, &ty), - |acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty), - ); - complete_methods(ctx, &ty, |func| { - acc.add_method(ctx, func, Some(hir::known::SELF_PARAM), None) - }); - } - } + let ty = self_param.ty(ctx.db); + complete_fields( + acc, + ctx, + &ty, + |acc, field, ty| acc.add_field(ctx, Some(hir::known::SELF_PARAM), field, &ty), + |acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty), + ); + complete_methods(ctx, &ty, |func| { + acc.add_method(ctx, func, Some(hir::known::SELF_PARAM), None) + }); } fn complete_fields( diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 6152ccb711fb..ecc1442bfc70 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -14,7 +14,9 @@ pub(crate) fn complete_expr_path( path_ctx: &PathCompletionCtx, ) { let _p = profile::span("complete_expr_path"); - + if !ctx.qualifier_ctx.none() { + return; + } let ( qualified, in_block_expr, @@ -23,6 +25,7 @@ pub(crate) fn complete_expr_path( after_if_expr, wants_mut_token, in_condition, + ty, ) = match path_ctx { &PathCompletionCtx { kind: @@ -33,10 +36,12 @@ pub(crate) fn complete_expr_path( in_condition, ref ref_expr_parent, ref is_func_update, + ref innermost_ret_ty, + .. }, ref qualified, .. - } if ctx.qualifier_ctx.none() => ( + } => ( qualified, in_block_expr, in_loop_body, @@ -44,6 +49,7 @@ pub(crate) fn complete_expr_path( after_if_expr, ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false), in_condition, + innermost_ret_ty, ), _ => return, }; @@ -252,10 +258,10 @@ pub(crate) fn complete_expr_path( } } - if let Some(fn_def) = &ctx.function_def { + if let Some(ty) = ty { add_keyword( "return", - match (in_block_expr, fn_def.ret_type().is_some()) { + match (in_block_expr, ty.is_unit()) { (true, true) => "return ;", (true, false) => "return;", (false, true) => "return $0", diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index d73cb6034d22..7df9b4921af5 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -95,6 +95,8 @@ pub(super) enum PathKind { in_condition: bool, ref_expr_parent: Option, is_func_update: Option, + self_param: Option, + innermost_ret_ty: Option, }, Type { location: TypeLocation, @@ -317,9 +319,6 @@ pub(crate) struct CompletionContext<'a> { /// The expected type of what we are completing. pub(super) expected_type: Option, - /// The parent function of the cursor position if it exists. - // FIXME: This probably doesn't belong here - pub(super) function_def: Option, /// The parent impl of the cursor position if it exists. // FIXME: This probably doesn't belong here pub(super) impl_def: Option, @@ -500,7 +499,6 @@ impl<'a> CompletionContext<'a> { module, expected_name: None, expected_type: None, - function_def: None, impl_def: None, incomplete_let: false, previous_token: None, diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index fc6ee70a0bcf..4f65ae402e2e 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -404,18 +404,6 @@ impl<'a> CompletionContext<'a> { ast::Item::Impl(impl_) => Some(impl_), _ => None, }); - self.function_def = self - .sema - .token_ancestors_with_macros(self.token.clone()) - .take_while(|it| { - it.kind() != SyntaxKind::SOURCE_FILE && it.kind() != SyntaxKind::MODULE - }) - .filter_map(ast::Item::cast) - .take(2) - .find_map(|it| match it { - ast::Item::Fn(fn_) => Some(fn_), - _ => None, - }); match name_like { ast::NameLike::Lifetime(lifetime) => { @@ -727,6 +715,56 @@ impl<'a> CompletionContext<'a> { let after_if_expr = after_if_expr(it.clone()); let ref_expr_parent = path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast); + let (innermost_ret_ty, self_param) = { + let find_ret_ty = |it: SyntaxNode| { + if let Some(item) = ast::Item::cast(it.clone()) { + match item { + ast::Item::Fn(f) => { + Some(sema.to_def(&f).map(|it| it.ret_type(sema.db))) + } + ast::Item::MacroCall(_) => None, + _ => Some(None), + } + } else { + let expr = ast::Expr::cast(it)?; + let callable = match expr { + // FIXME + // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b), + ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr), + _ => return None, + }; + Some( + callable + .and_then(|c| c.adjusted().as_callable(sema.db)) + .map(|it| it.return_type()), + ) + } + }; + let find_fn_self_param = |it| match it { + ast::Item::Fn(fn_) => { + Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db))) + } + ast::Item::MacroCall(_) => None, + _ => Some(None), + }; + + match dbg!(find_node_in_file_compensated(original_file, &expr)) { + Some(it) => { + let innermost_ret_ty = sema + .ancestors_with_macros(it.syntax().clone()) + .find_map(find_ret_ty) + .flatten(); + + let self_param = sema + .ancestors_with_macros(it.syntax().clone()) + .filter_map(ast::Item::cast) + .find_map(find_fn_self_param) + .flatten(); + (innermost_ret_ty, self_param) + } + None => (None, None), + } + }; let is_func_update = func_update_record(it); let in_condition = is_in_condition(&expr); @@ -737,6 +775,8 @@ impl<'a> CompletionContext<'a> { in_condition, ref_expr_parent, is_func_update, + innermost_ret_ty, + self_param, } }; let make_path_kind_type = |ty: ast::Type| { From 83e8f3ac308aab3c51158c6a27491a4e8796f361 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 18 Jun 2022 10:18:56 +0200 Subject: [PATCH 2/4] Move `CompletionContext::incomplete_let` into `PathKind::Expr` --- crates/ide-completion/src/completions.rs | 20 +++++++++++++++++-- crates/ide-completion/src/completions/expr.rs | 6 +++++- crates/ide-completion/src/context.rs | 5 +---- crates/ide-completion/src/context/analysis.rs | 10 +++++----- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index b0763584432b..f8747b072492 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -110,12 +110,18 @@ impl Completions { ["self", "super", "crate"].into_iter().for_each(|kw| self.add_keyword(ctx, kw)); } - pub(crate) fn add_keyword_snippet(&mut self, ctx: &CompletionContext, kw: &str, snippet: &str) { + pub(crate) fn add_keyword_snippet_expr( + &mut self, + ctx: &CompletionContext, + kw: &str, + snippet: &str, + incomplete_let: bool, + ) { let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw); match ctx.config.snippet_cap { Some(cap) => { - if snippet.ends_with('}') && ctx.incomplete_let { + if snippet.ends_with('}') && incomplete_let { // complete block expression snippets with a trailing semicolon, if inside an incomplete let cov_mark::hit!(let_semi); item.insert_snippet(cap, format!("{};", snippet)); @@ -130,6 +136,16 @@ impl Completions { item.add_to(self); } + pub(crate) fn add_keyword_snippet(&mut self, ctx: &CompletionContext, kw: &str, snippet: &str) { + let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw); + + match ctx.config.snippet_cap { + Some(cap) => item.insert_snippet(cap, snippet), + None => item.insert_text(if snippet.contains('$') { kw } else { snippet }), + }; + item.add_to(self); + } + pub(crate) fn add_crate_roots(&mut self, ctx: &CompletionContext) { ctx.process_all_names(&mut |name, res| match res { ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => { diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index ecc1442bfc70..e4d1c290c07c 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -26,6 +26,7 @@ pub(crate) fn complete_expr_path( wants_mut_token, in_condition, ty, + incomplete_let, ) = match path_ctx { &PathCompletionCtx { kind: @@ -34,6 +35,7 @@ pub(crate) fn complete_expr_path( in_loop_body, after_if_expr, in_condition, + incomplete_let, ref ref_expr_parent, ref is_func_update, ref innermost_ret_ty, @@ -50,6 +52,7 @@ pub(crate) fn complete_expr_path( ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false), in_condition, innermost_ret_ty, + incomplete_let, ), _ => return, }; @@ -220,7 +223,8 @@ pub(crate) fn complete_expr_path( }); if !is_func_update { - let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet); + let mut add_keyword = + |kw, snippet| acc.add_keyword_snippet_expr(ctx, kw, snippet, incomplete_let); if !in_block_expr { add_keyword("unsafe", "unsafe {\n $0\n}"); diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 7df9b4921af5..bc2c2fc71323 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -93,6 +93,7 @@ pub(super) enum PathKind { after_if_expr: bool, /// Whether this expression is the direct condition of an if or while expression in_condition: bool, + incomplete_let: bool, ref_expr_parent: Option, is_func_update: Option, self_param: Option, @@ -322,9 +323,6 @@ pub(crate) struct CompletionContext<'a> { /// The parent impl of the cursor position if it exists. // FIXME: This probably doesn't belong here pub(super) impl_def: Option, - /// Are we completing inside a let statement with a missing semicolon? - // FIXME: This should be part of PathKind::Expr - pub(super) incomplete_let: bool, // FIXME: This shouldn't exist pub(super) previous_token: Option, @@ -500,7 +498,6 @@ impl<'a> CompletionContext<'a> { expected_name: None, expected_type: None, impl_def: None, - incomplete_let: false, previous_token: None, // dummy value, will be overwritten ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None }, diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 4f65ae402e2e..d416d8251d75 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -330,11 +330,6 @@ impl<'a> CompletionContext<'a> { self.previous_token = syntax_element.clone().into_token().and_then(previous_non_trivia_token); - self.incomplete_let = - syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { - it.syntax().text_range().end() == syntax_element.text_range().end() - }); - (self.expected_type, self.expected_name) = self.expected_type_and_name(); // Overwrite the path kind for derives @@ -767,6 +762,10 @@ impl<'a> CompletionContext<'a> { }; let is_func_update = func_update_record(it); let in_condition = is_in_condition(&expr); + let incomplete_let = it + .parent() + .and_then(ast::LetStmt::cast) + .map_or(false, |it| it.semicolon_token().is_none()); PathKind::Expr { in_block_expr, @@ -777,6 +776,7 @@ impl<'a> CompletionContext<'a> { is_func_update, innermost_ret_ty, self_param, + incomplete_let, } }; let make_path_kind_type = |ty: ast::Type| { From c1446a27437c415e262f8a2850f27f9615f84c14 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 18 Jun 2022 10:45:53 +0200 Subject: [PATCH 3/4] Move `CompletionContext::impl_def` into corresponding entities --- crates/ide-completion/src/completions.rs | 4 +- crates/ide-completion/src/completions/expr.rs | 18 +++-- .../src/completions/fn_param.rs | 14 ++-- .../src/completions/item_list.rs | 2 +- .../src/completions/item_list/trait_impl.rs | 12 ++-- .../ide-completion/src/completions/pattern.rs | 12 +++- crates/ide-completion/src/context.rs | 11 ++- crates/ide-completion/src/context/analysis.rs | 72 ++++++++++++++----- 8 files changed, 97 insertions(+), 48 deletions(-) diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index f8747b072492..284372f7fc51 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -24,6 +24,7 @@ use std::iter; use hir::{db::HirDatabase, known, ScopeDef}; use ide_db::SymbolKind; +use syntax::ast; use crate::{ context::Visible, @@ -409,11 +410,12 @@ fn enum_variants_with_paths( acc: &mut Completions, ctx: &CompletionContext, enum_: hir::Enum, + impl_: &Option, cb: impl Fn(&mut Completions, &CompletionContext, hir::Variant, hir::ModPath), ) { let variants = enum_.variants(ctx.db); - if let Some(impl_) = ctx.impl_def.as_ref().and_then(|impl_| ctx.sema.to_def(impl_)) { + if let Some(impl_) = impl_.as_ref().and_then(|impl_| ctx.sema.to_def(impl_)) { if impl_.self_ty(ctx.db).as_adt() == Some(hir::Adt::Enum(enum_)) { for &variant in &variants { let self_path = hir::ModPath::from_segments( diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index e4d1c290c07c..83ecb51aaabd 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -27,6 +27,7 @@ pub(crate) fn complete_expr_path( in_condition, ty, incomplete_let, + impl_, ) = match path_ctx { &PathCompletionCtx { kind: @@ -39,6 +40,7 @@ pub(crate) fn complete_expr_path( ref ref_expr_parent, ref is_func_update, ref innermost_ret_ty, + ref impl_, .. }, ref qualified, @@ -53,6 +55,7 @@ pub(crate) fn complete_expr_path( in_condition, innermost_ret_ty, incomplete_let, + impl_, ), _ => return, }; @@ -181,8 +184,7 @@ pub(crate) fn complete_expr_path( if let Some(adt) = ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) { - let self_ty = - (|| ctx.sema.to_def(ctx.impl_def.as_ref()?)?.self_ty(ctx.db).as_adt())(); + let self_ty = (|| ctx.sema.to_def(impl_.as_ref()?)?.self_ty(ctx.db).as_adt())(); let complete_self = self_ty == Some(adt); match adt { @@ -210,9 +212,15 @@ pub(crate) fn complete_expr_path( } } hir::Adt::Enum(e) => { - super::enum_variants_with_paths(acc, ctx, e, |acc, ctx, variant, path| { - acc.add_qualified_enum_variant(ctx, variant, path) - }); + super::enum_variants_with_paths( + acc, + ctx, + e, + impl_, + |acc, ctx, variant, path| { + acc.add_qualified_enum_variant(ctx, variant, path) + }, + ); } } } diff --git a/crates/ide-completion/src/completions/fn_param.rs b/crates/ide-completion/src/completions/fn_param.rs index a62b966e0198..94e2f489f57f 100644 --- a/crates/ide-completion/src/completions/fn_param.rs +++ b/crates/ide-completion/src/completions/fn_param.rs @@ -24,8 +24,8 @@ pub(crate) fn complete_fn_param( ctx: &CompletionContext, pattern_ctx: &PatternContext, ) -> Option<()> { - let (param_list, _, param_kind) = match pattern_ctx { - PatternContext { param_ctx: Some(kind), .. } => kind, + let ((param_list, _, param_kind), impl_) = match pattern_ctx { + PatternContext { param_ctx: Some(kind), impl_, .. } => (kind, impl_), _ => return None, }; @@ -45,7 +45,7 @@ pub(crate) fn complete_fn_param( match param_kind { ParamKind::Function(function) => { - fill_fn_params(ctx, function, param_list, add_new_item_to_acc); + fill_fn_params(ctx, function, param_list, impl_, add_new_item_to_acc); } ParamKind::Closure(closure) => { let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?; @@ -62,6 +62,7 @@ fn fill_fn_params( ctx: &CompletionContext, function: &ast::Fn, param_list: &ast::ParamList, + impl_: &Option, mut add_new_item_to_acc: impl FnMut(&str), ) { let mut file_params = FxHashMap::default(); @@ -104,7 +105,7 @@ fn fill_fn_params( } remove_duplicated(&mut file_params, param_list.params()); let self_completion_items = ["self", "&self", "mut self", "&mut self"]; - if should_add_self_completions(ctx, param_list) { + if should_add_self_completions(param_list, impl_) { self_completion_items.into_iter().for_each(|self_item| add_new_item_to_acc(self_item)); } @@ -155,11 +156,10 @@ fn remove_duplicated( }) } -fn should_add_self_completions(ctx: &CompletionContext, param_list: &ast::ParamList) -> bool { - let inside_impl = ctx.impl_def.is_some(); +fn should_add_self_completions(param_list: &ast::ParamList, impl_: &Option) -> bool { let no_params = param_list.params().next().is_none() && param_list.self_param().is_none(); - inside_impl && no_params + impl_.is_some() && no_params } fn comma_wrapper(ctx: &CompletionContext) -> Option<(impl Fn(&str) -> String, TextRange)> { diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index f4402a3f87a3..941273fa971c 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -66,7 +66,7 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext, kind: Option<&It let in_assoc_non_trait_impl = matches!(kind, Some(ItemListKind::Impl | ItemListKind::Trait)); let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock)); let in_trait = matches!(kind, Some(ItemListKind::Trait)); - let in_trait_impl = matches!(kind, Some(ItemListKind::TraitImpl)); + let in_trait_impl = matches!(kind, Some(ItemListKind::TraitImpl(_))); let in_inherent_impl = matches!(kind, Some(ItemListKind::Impl)); let no_qualifiers = ctx.qualifier_ctx.vis_node.is_none(); let in_block = matches!(kind, None); diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs index c0c29d4cd22f..58b894bdd432 100644 --- a/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -81,7 +81,7 @@ pub(crate) fn complete_trait_impl_name( kind, replacement_range(ctx, &item), // item -> ASSOC_ITEM_LIST -> IMPL - ast::Impl::cast(item.parent()?.parent()?)?, + &ast::Impl::cast(item.parent()?.parent()?)?, ); Some(()) } @@ -97,7 +97,7 @@ pub(crate) fn complete_trait_impl_name_ref( kind: NameRefKind::Path( path_ctx @ PathCompletionCtx { - kind: PathKind::Item { kind: ItemListKind::TraitImpl }, + kind: PathKind::Item { kind: ItemListKind::TraitImpl(Some(impl_)) }, .. }, ), @@ -109,7 +109,7 @@ pub(crate) fn complete_trait_impl_name_ref( Some(name) => name.syntax().text_range(), None => ctx.source_range(), }, - ctx.impl_def.clone()?, + impl_, ), _ => (), } @@ -121,10 +121,10 @@ fn complete_trait_impl( ctx: &CompletionContext, kind: ImplCompletionKind, replacement_range: TextRange, - impl_def: ast::Impl, + impl_def: &ast::Impl, ) { - if let Some(hir_impl) = ctx.sema.to_def(&impl_def) { - get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| { + if let Some(hir_impl) = ctx.sema.to_def(impl_def) { + get_missing_assoc_items(&ctx.sema, impl_def).into_iter().for_each(|item| { use self::ImplCompletionKind::*; match (item, kind) { (hir::AssocItem::Function(func), All | Fn) => { diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs index dd08ef703e44..149acb3c1bec 100644 --- a/crates/ide-completion/src/completions/pattern.rs +++ b/crates/ide-completion/src/completions/pattern.rs @@ -51,9 +51,15 @@ pub(crate) fn complete_pattern( ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) { if refutable || single_variant_enum(e) { - super::enum_variants_with_paths(acc, ctx, e, |acc, ctx, variant, path| { - acc.add_qualified_variant_pat(ctx, variant, path); - }); + super::enum_variants_with_paths( + acc, + ctx, + e, + &patctx.impl_, + |acc, ctx, variant, path| { + acc.add_qualified_variant_pat(ctx, variant, path); + }, + ); } } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index bc2c2fc71323..0c7c6ab5af6c 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -98,6 +98,7 @@ pub(super) enum PathKind { is_func_update: Option, self_param: Option, innermost_ret_ty: Option, + impl_: Option, }, Type { location: TypeLocation, @@ -143,12 +144,12 @@ pub(crate) enum TypeAscriptionTarget { } /// The kind of item list a [`PathKind::Item`] belongs to. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub(super) enum ItemListKind { SourceFile, Module, Impl, - TraitImpl, + TraitImpl(Option), Trait, ExternBlock, } @@ -179,6 +180,7 @@ pub(super) struct PatternContext { pub(super) mut_token: Option, /// The record pattern this name or ref is a field of pub(super) record_pat: Option, + pub(super) impl_: Option, } /// The state of the lifetime we are completing. @@ -320,10 +322,6 @@ pub(crate) struct CompletionContext<'a> { /// The expected type of what we are completing. pub(super) expected_type: Option, - /// The parent impl of the cursor position if it exists. - // FIXME: This probably doesn't belong here - pub(super) impl_def: Option, - // FIXME: This shouldn't exist pub(super) previous_token: Option, @@ -497,7 +495,6 @@ impl<'a> CompletionContext<'a> { module, expected_name: None, expected_type: None, - impl_def: None, previous_token: None, // dummy value, will be overwritten ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None }, diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index d416d8251d75..e08fa6bdd356 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -389,16 +389,6 @@ impl<'a> CompletionContext<'a> { return Some(()); } }; - self.impl_def = self - .sema - .token_ancestors_with_macros(self.token.clone()) - .take_while(|it| it.kind() != SyntaxKind::SOURCE_FILE) - .filter_map(ast::Item::cast) - .take(2) - .find_map(|it| match it { - ast::Item::Impl(impl_) => Some(impl_), - _ => None, - }); match name_like { ast::NameLike::Lifetime(lifetime) => { @@ -452,7 +442,7 @@ impl<'a> CompletionContext<'a> { } fn classify_name( - _sema: &Semantics, + sema: &Semantics, original_file: &SyntaxNode, name: ast::Name, ) -> Option { @@ -464,7 +454,7 @@ impl<'a> CompletionContext<'a> { ast::Enum(_) => NameKind::Enum, ast::Fn(_) => NameKind::Function, ast::IdentPat(bind_pat) => { - let mut pat_ctx = pattern_context_for(original_file, bind_pat.into()); + let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into()); if let Some(record_field) = ast::RecordPatField::for_field_name(&name) { pat_ctx.record_pat = find_node_in_file_compensated(original_file, &record_field.parent_record_pat()); } @@ -518,6 +508,7 @@ impl<'a> CompletionContext<'a> { &record_field.parent_record_pat(), ), ..pattern_context_for( + sema, original_file, record_field.parent_record_pat().clone().into(), ) @@ -766,6 +757,7 @@ impl<'a> CompletionContext<'a> { .parent() .and_then(ast::LetStmt::cast) .map_or(false, |it| it.semicolon_token().is_none()); + let impl_ = fetch_immediate_impl(sema, original_file, &expr); PathKind::Expr { in_block_expr, @@ -777,6 +769,7 @@ impl<'a> CompletionContext<'a> { innermost_ret_ty, self_param, incomplete_let, + impl_, } }; let make_path_kind_type = |ty: ast::Type| { @@ -804,14 +797,14 @@ impl<'a> CompletionContext<'a> { }, ast::TupleStructPat(it) => { path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(original_file, it.into())} + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} }, ast::RecordPat(it) => { path_ctx.has_call_parens = true; - PathKind::Pat { pat_ctx: pattern_context_for(original_file, it.into())} + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} }, ast::PathPat(it) => { - PathKind::Pat { pat_ctx: pattern_context_for(original_file, it.into())} + PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())} }, ast::MacroCall(it) => { // A macro call in this position is usually a result of parsing recovery, so check that @@ -825,7 +818,7 @@ impl<'a> CompletionContext<'a> { match_ast! { match parent { ast::MacroExpr(expr) => make_path_kind_expr(expr.into()), - ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(original_file, it.into())}, + ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}, ast::MacroType(ty) => make_path_kind_type(ty.into()), ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module }, ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() { @@ -833,7 +826,7 @@ impl<'a> CompletionContext<'a> { match it { ast::Trait(_) => ItemListKind::Trait, ast::Impl(it) => if it.trait_().is_some() { - ItemListKind::TraitImpl + ItemListKind::TraitImpl(find_node_in_file_compensated(original_file, &it)) } else { ItemListKind::Impl }, @@ -970,7 +963,11 @@ impl<'a> CompletionContext<'a> { } } -fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternContext { +fn pattern_context_for( + sema: &Semantics, + original_file: &SyntaxNode, + pat: ast::Pat, +) -> PatternContext { let mut is_param = None; let (refutability, has_type_ascription) = pat @@ -1011,6 +1008,7 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont ast::Pat::IdentPat(it) => (it.ref_token(), it.mut_token()), _ => (None, None), }; + PatternContext { refutability, param_ctx: is_param, @@ -1019,6 +1017,44 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont mut_token, ref_token, record_pat: None, + impl_: fetch_immediate_impl(sema, original_file, &pat), + } +} + +fn fetch_immediate_impl( + sema: &Semantics, + original_file: &SyntaxNode, + node: &impl AstNode, +) -> Option { + // FIXME: The fallback here could be done better + let (f, s) = match find_node_in_file_compensated(original_file, node) { + Some(node) => { + let mut items = sema + .ancestors_with_macros(node.syntax().clone()) + .filter_map(ast::Item::cast) + .filter(|it| !matches!(it, ast::Item::MacroCall(_))) + .take(2); + (items.next(), items.next()) + } + None => { + let mut items = node + .syntax() + .ancestors() + .filter_map(ast::Item::cast) + .filter(|it| !matches!(it, ast::Item::MacroCall(_))) + .take(2); + (items.next(), items.next()) + } + }; + + match f? { + ast::Item::Const(_) | ast::Item::Fn(_) | ast::Item::TypeAlias(_) => (), + ast::Item::Impl(it) => return Some(it), + _ => return None, + } + match s? { + ast::Item::Impl(it) => Some(it), + _ => None, } } From f271b18129f2faa93e0e1ac0de08062e5fd975a1 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 18 Jun 2022 11:19:36 +0200 Subject: [PATCH 4/4] Consider walking up macro expansions when searching for surrounding entities in completion analysis --- crates/ide-completion/src/context/analysis.rs | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index e08fa6bdd356..9ceb60f90f16 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -456,7 +456,7 @@ impl<'a> CompletionContext<'a> { ast::IdentPat(bind_pat) => { let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into()); if let Some(record_field) = ast::RecordPatField::for_field_name(&name) { - pat_ctx.record_pat = find_node_in_file_compensated(original_file, &record_field.parent_record_pat()); + pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat()); } NameKind::IdentPat(pat_ctx) @@ -493,9 +493,13 @@ impl<'a> CompletionContext<'a> { |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default()); if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { - return find_node_in_file_compensated(original_file, &record_field.parent_record_lit()) - .map(NameRefKind::RecordExpr) - .map(make_res); + return find_node_in_file_compensated( + sema, + original_file, + &record_field.parent_record_lit(), + ) + .map(NameRefKind::RecordExpr) + .map(make_res); } if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { let kind = NameRefKind::Pattern(PatternContext { @@ -504,6 +508,7 @@ impl<'a> CompletionContext<'a> { ref_token: None, mut_token: None, record_pat: find_node_in_file_compensated( + sema, original_file, &record_field.parent_record_pat(), ), @@ -568,7 +573,7 @@ impl<'a> CompletionContext<'a> { }; let func_update_record = |syn: &SyntaxNode| { if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) { - find_node_in_file_compensated(original_file, &record_expr) + find_node_in_file_compensated(sema, original_file, &record_expr) } else { None } @@ -670,9 +675,9 @@ impl<'a> CompletionContext<'a> { ast::TypeBound(_) => TypeLocation::TypeBound, // is this case needed? ast::TypeBoundList(_) => TypeLocation::TypeBound, - ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))), + ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))), // is this case needed? - ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(original_file, Some(it))), + ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))), ast::TupleField(_) => TypeLocation::TupleField, _ => return None, } @@ -734,7 +739,7 @@ impl<'a> CompletionContext<'a> { _ => Some(None), }; - match dbg!(find_node_in_file_compensated(original_file, &expr)) { + match find_node_in_file_compensated(sema, original_file, &expr) { Some(it) => { let innermost_ret_ty = sema .ancestors_with_macros(it.syntax().clone()) @@ -757,7 +762,7 @@ impl<'a> CompletionContext<'a> { .parent() .and_then(ast::LetStmt::cast) .map_or(false, |it| it.semicolon_token().is_none()); - let impl_ = fetch_immediate_impl(sema, original_file, &expr); + let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax()); PathKind::Expr { in_block_expr, @@ -826,7 +831,7 @@ impl<'a> CompletionContext<'a> { match it { ast::Trait(_) => ItemListKind::Trait, ast::Impl(it) => if it.trait_().is_some() { - ItemListKind::TraitImpl(find_node_in_file_compensated(original_file, &it)) + ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it)) } else { ItemListKind::Impl }, @@ -983,7 +988,7 @@ fn pattern_context_for( let has_type_ascription = param.ty().is_some(); is_param = (|| { let fake_param_list = param.syntax().parent().and_then(ast::ParamList::cast)?; - let param_list = find_node_in_file_compensated(original_file, &fake_param_list)?; + let param_list = find_node_in_file_compensated(sema, original_file, &fake_param_list)?; let param_list_owner = param_list.syntax().parent()?; let kind = match_ast! { match param_list_owner { @@ -1017,42 +1022,25 @@ fn pattern_context_for( mut_token, ref_token, record_pat: None, - impl_: fetch_immediate_impl(sema, original_file, &pat), + impl_: fetch_immediate_impl(sema, original_file, pat.syntax()), } } fn fetch_immediate_impl( sema: &Semantics, original_file: &SyntaxNode, - node: &impl AstNode, + node: &SyntaxNode, ) -> Option { - // FIXME: The fallback here could be done better - let (f, s) = match find_node_in_file_compensated(original_file, node) { - Some(node) => { - let mut items = sema - .ancestors_with_macros(node.syntax().clone()) - .filter_map(ast::Item::cast) - .filter(|it| !matches!(it, ast::Item::MacroCall(_))) - .take(2); - (items.next(), items.next()) - } - None => { - let mut items = node - .syntax() - .ancestors() - .filter_map(ast::Item::cast) - .filter(|it| !matches!(it, ast::Item::MacroCall(_))) - .take(2); - (items.next(), items.next()) - } - }; + let mut ancestors = ancestors_in_file_compensated(sema, original_file, node)? + .filter_map(ast::Item::cast) + .filter(|it| !matches!(it, ast::Item::MacroCall(_))); - match f? { + match ancestors.next()? { ast::Item::Const(_) | ast::Item::Fn(_) | ast::Item::TypeAlias(_) => (), ast::Item::Impl(it) => return Some(it), _ => return None, } - match s? { + match ancestors.next()? { ast::Item::Impl(it) => Some(it), _ => None, } @@ -1076,9 +1064,21 @@ fn find_node_in_file(syntax: &SyntaxNode, node: &N) -> Option { /// Attempts to find `node` inside `syntax` via `node`'s text range while compensating /// for the offset introduced by the fake ident. /// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead. -fn find_node_in_file_compensated(syntax: &SyntaxNode, node: &N) -> Option { - let syntax_range = syntax.text_range(); - let range = node.syntax().text_range(); +fn find_node_in_file_compensated( + sema: &Semantics, + in_file: &SyntaxNode, + node: &N, +) -> Option { + ancestors_in_file_compensated(sema, in_file, node.syntax())?.find_map(N::cast) +} + +fn ancestors_in_file_compensated<'sema>( + sema: &'sema Semantics, + in_file: &SyntaxNode, + node: &SyntaxNode, +) -> Option + 'sema> { + let syntax_range = in_file.text_range(); + let range = node.text_range(); let end = range.end().checked_sub(TextSize::try_from(COMPLETION_MARKER.len()).ok()?)?; if end < range.start() { return None; @@ -1086,17 +1086,22 @@ fn find_node_in_file_compensated(syntax: &SyntaxNode, node: &N) -> O let range = TextRange::new(range.start(), end); // our inserted ident could cause `range` to go outside of the original syntax, so cap it let intersection = range.intersect(syntax_range)?; - syntax.covering_element(intersection).ancestors().find_map(N::cast) + let node = match in_file.covering_element(intersection) { + NodeOrToken::Node(node) => node, + NodeOrToken::Token(tok) => tok.parent()?, + }; + Some(sema.ancestors_with_macros(node)) } /// Attempts to find `node` inside `syntax` via `node`'s text range while compensating /// for the offset introduced by the fake ident.. /// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead. fn find_opt_node_in_file_compensated( + sema: &Semantics, syntax: &SyntaxNode, node: Option, ) -> Option { - find_node_in_file_compensated(syntax, &node?) + find_node_in_file_compensated(sema, syntax, &node?) } fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {