diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 3d92a0cceffe4..e97545eae50d0 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -2,7 +2,6 @@ use hir::ScopeDef; use ide_db::FxHashSet; -use syntax::T; use crate::{ context::{NameRefContext, NameRefKind, PathCompletionCtx, PathKind, PathQualifierCtx}, @@ -20,6 +19,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) is_func_update, after_if_expr, wants_mut_token, + in_condition, ) = match ctx.nameref_ctx() { Some(&NameRefContext { kind: @@ -29,6 +29,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) in_block_expr, in_loop_body, after_if_expr, + in_condition, ref ref_expr_parent, ref is_func_update, }, @@ -45,6 +46,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) is_func_update.is_some(), after_if_expr, ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false), + in_condition, ), _ => return, }; @@ -235,10 +237,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) add_keyword("true", "true"); add_keyword("false", "false"); - if ctx.previous_token_is(T![if]) - || ctx.previous_token_is(T![while]) - || in_block_expr - { + if (in_condition && !is_absolute_path) || in_block_expr { add_keyword("let", "let"); } diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index 9d3a1c24293d7..3b2e383a09d7c 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -34,7 +34,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) matches!(location, TypeLocation::GenericArgList(_)) } ScopeDef::ImplSelfType(_) => { - !ctx.previous_token_is(syntax::T![impl]) && !ctx.previous_token_is(syntax::T![for]) + !matches!(location, TypeLocation::ImplTarget | TypeLocation::ImplTrait) } // Don't suggest attribute macros and derives. ScopeDef::ModuleDef(Macro(mac)) => mac.is_fn_like(ctx.db), diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index d5f754d66581c..767ea5c20de25 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -92,6 +92,8 @@ pub(super) enum PathKind { in_block_expr: bool, in_loop_body: bool, after_if_expr: bool, + /// Whether this expression is the direct condition of an if or while expression + in_condition: bool, ref_expr_parent: Option, is_func_update: Option, }, @@ -121,6 +123,8 @@ pub(crate) enum TypeLocation { TypeAscription(TypeAscriptionTarget), GenericArgList(Option), TypeBound, + ImplTarget, + ImplTrait, Other, } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index dc50d82f1998c..c672c2761a519 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -327,7 +327,8 @@ impl<'a> CompletionContext<'a> { return None; } - self.previous_token = previous_token(syntax_element.clone()); + 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| { @@ -647,8 +648,8 @@ impl<'a> CompletionContext<'a> { None }; - let type_location = |it: Option| { - let parent = it?; + let type_location = |node: &SyntaxNode| { + let parent = node.parent()?; let res = match_ast! { match parent { ast::Const(it) => { @@ -690,6 +691,15 @@ impl<'a> CompletionContext<'a> { } TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat()))) }, + ast::Impl(it) => { + match it.trait_() { + Some(t) if t.syntax() == node => TypeLocation::ImplTrait, + _ => match it.self_ty() { + Some(t) if t.syntax() == node => TypeLocation::ImplTarget, + _ => return None, + }, + } + }, ast::TypeBound(_) => TypeLocation::TypeBound, // is this case needed? ast::TypeBoundList(_) => TypeLocation::TypeBound, @@ -703,16 +713,49 @@ impl<'a> CompletionContext<'a> { Some(res) }; + let is_in_condition = |it: &ast::Expr| { + (|| { + let parent = it.syntax().parent()?; + if let Some(expr) = ast::WhileExpr::cast(parent.clone()) { + Some(expr.condition()? == *it) + } else if let Some(expr) = ast::IfExpr::cast(parent) { + Some(expr.condition()? == *it) + } else { + None + } + })() + .unwrap_or(false) + }; + + let make_path_kind_expr = |expr: ast::Expr| { + let it = expr.syntax(); + let in_block_expr = is_in_block(it); + let in_loop_body = is_in_loop_body(it); + 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 is_func_update = func_update_record(it); + let in_condition = is_in_condition(&expr); + + PathKind::Expr { + in_block_expr, + in_loop_body, + after_if_expr, + in_condition, + ref_expr_parent, + is_func_update, + } + }; + let make_path_kind_type = |ty: ast::Type| { + let location = type_location(ty.syntax()); + PathKind::Type { location: location.unwrap_or(TypeLocation::Other) } + }; + // Infer the path kind let kind = path.syntax().parent().and_then(|it| { match_ast! { match it { - ast::PathType(it) => { - let location = type_location(it.syntax().parent()); - Some(PathKind::Type { - location: location.unwrap_or(TypeLocation::Other), - }) - }, + ast::PathType(it) => Some(make_path_kind_type(it.into())), ast::PathExpr(it) => { if let Some(p) = it.syntax().parent() { if ast::ExprStmt::can_cast(p.kind()) { @@ -724,14 +767,8 @@ impl<'a> CompletionContext<'a> { } path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); - let in_block_expr = is_in_block(it.syntax()); - let in_loop_body = is_in_loop_body(it.syntax()); - let after_if_expr = after_if_expr(it.syntax().clone()); - let ref_expr_parent = path.as_single_name_ref() - .and_then(|_| it.syntax().parent()).and_then(ast::RefExpr::cast); - let is_func_update = func_update_record(it.syntax()); - - Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent, is_func_update }) + + Some(make_path_kind_expr(it.into())) }, ast::TupleStructPat(it) => { path_ctx.has_call_parens = true; @@ -748,50 +785,41 @@ impl<'a> CompletionContext<'a> { Some(PathKind::Pat) }, ast::MacroCall(it) => { + // A macro call in this position is usually a result of parsing recovery, so check that if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) { nameref_ctx.kind = Some(NameRefKind::Keyword(kind)); return None; } path_ctx.has_macro_bang = it.excl_token().is_some(); - let parent = it.syntax().parent(); - match parent.as_ref().map(|it| it.kind()) { - Some(SyntaxKind::MACRO_PAT) => Some(PathKind::Pat), - Some(SyntaxKind::MACRO_TYPE) => { - let location = type_location(parent.unwrap().parent()); - Some(PathKind::Type { - location: location.unwrap_or(TypeLocation::Other), - }) - }, - Some(SyntaxKind::ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::Module }), - Some(SyntaxKind::ASSOC_ITEM_LIST) => Some(PathKind::Item { kind: match parent.and_then(|it| it.parent()) { - Some(it) => match_ast! { - match it { - ast::Trait(_) => ItemListKind::Trait, - ast::Impl(it) => if it.trait_().is_some() { - ItemListKind::TraitImpl - } else { - ItemListKind::Impl - }, - _ => return None - } - }, - None => return None, - } }), - Some(SyntaxKind::EXTERN_ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::ExternBlock }), - Some(SyntaxKind::SOURCE_FILE) => Some(PathKind::Item { kind: ItemListKind::SourceFile }), - _ => { - return parent.and_then(ast::MacroExpr::cast).map(|it| { - let in_loop_body = is_in_loop_body(it.syntax()); - let in_block_expr = is_in_block(it.syntax()); - let after_if_expr = after_if_expr(it.syntax().clone()); - let ref_expr_parent = path.as_single_name_ref() - .and_then(|_| it.syntax().parent()).and_then(ast::RefExpr::cast); - let is_func_update = func_update_record(it.syntax()); - PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent, is_func_update } - }); - }, - } + let parent = it.syntax().parent()?; + // Any path in an item list will be treated as a macro call by the parser + let res = match_ast! { + match parent { + ast::MacroExpr(expr) => make_path_kind_expr(expr.into()), + ast::MacroPat(_) => PathKind::Pat, + ast::MacroType(ty) => make_path_kind_type(ty.into()), + ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module }, + ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() { + Some(it) => match_ast! { + match it { + ast::Trait(_) => ItemListKind::Trait, + ast::Impl(it) => if it.trait_().is_some() { + ItemListKind::TraitImpl + } else { + ItemListKind::Impl + }, + _ => return None + } + }, + None => return None, + } }, + ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock }, + ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile }, + _ => return None, + } + }; + Some(res) }, ast::Meta(meta) => (|| { let attr = meta.parent_attr()?; @@ -818,10 +846,13 @@ impl<'a> CompletionContext<'a> { match kind { Some(kind) => path_ctx.kind = kind, + // unresolved path kind, so this isn't really a path we should be completing, + // just some random identifier which might be in keyword position None => return res, } path_ctx.has_type_args = segment.generic_arg_list().is_some(); + // calculate the qualifier context if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) { if !use_tree_parent { path_ctx.is_absolute_path = @@ -1034,10 +1065,6 @@ fn has_ref(token: &SyntaxToken) -> bool { token.kind() == T![&] } -pub(crate) fn previous_token(element: SyntaxElement) -> Option { - element.into_token().and_then(previous_non_trivia_token) -} - pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool { // oh my ... (|| {