diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index 4eb1fccd7d32..ade57bed958e 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -4,7 +4,8 @@ use ide_db::FxHashSet; use crate::{ context::{ - CompletionContext, DotAccess, DotAccessKind, NameRefContext, PathCompletionCtx, PathKind, + CompletionContext, DotAccess, DotAccessKind, NameRefContext, NameRefKind, + PathCompletionCtx, PathKind, }, CompletionItem, CompletionItemKind, Completions, }; @@ -13,7 +14,8 @@ use crate::{ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { let (dot_access, receiver_ty) = match ctx.nameref_ctx() { Some(NameRefContext { - dot_access: Some(access @ DotAccess { receiver_ty: Some(receiver_ty), .. }), + kind: + Some(NameRefKind::DotAccess(access @ DotAccess { receiver_ty: Some(receiver_ty), .. })), .. }) => (access, &receiver_ty.original), _ => return complete_undotted_self(acc, ctx), diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 7c3296a0b31b..3d92a0cceffe 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -5,7 +5,7 @@ use ide_db::FxHashSet; use syntax::T; use crate::{ - context::{NameRefContext, PathCompletionCtx, PathKind, PathQualifierCtx}, + context::{NameRefContext, NameRefKind, PathCompletionCtx, PathKind, PathQualifierCtx}, CompletionContext, Completions, }; @@ -21,24 +21,29 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) after_if_expr, wants_mut_token, ) = match ctx.nameref_ctx() { - Some(NameRefContext { - path_ctx: - Some(PathCompletionCtx { + Some(&NameRefContext { + kind: + Some(NameRefKind::Path(PathCompletionCtx { kind: - PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent }, + PathKind::Expr { + in_block_expr, + in_loop_body, + after_if_expr, + ref ref_expr_parent, + ref is_func_update, + }, is_absolute_path, - qualifier, + ref qualifier, .. - }), - record_expr, + })), .. }) if ctx.qualifier_ctx.none() => ( - *is_absolute_path, + is_absolute_path, qualifier, - *in_block_expr, - *in_loop_body, - record_expr.as_ref().map_or(false, |&(_, it)| it), - *after_if_expr, + in_block_expr, + in_loop_body, + is_func_update.is_some(), + after_if_expr, ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false), ), _ => return, diff --git a/crates/ide-completion/src/completions/field.rs b/crates/ide-completion/src/completions/field.rs index 17395279178c..c540b87a46fd 100644 --- a/crates/ide-completion/src/completions/field.rs +++ b/crates/ide-completion/src/completions/field.rs @@ -1,7 +1,10 @@ //! Completion of field list position. use crate::{ - context::{IdentContext, NameContext, NameKind, NameRefContext, PathCompletionCtx, PathKind}, + context::{ + IdentContext, NameContext, NameKind, NameRefContext, NameRefKind, PathCompletionCtx, + PathKind, TypeLocation, + }, CompletionContext, Completions, }; @@ -9,16 +12,16 @@ pub(crate) fn complete_field_list(acc: &mut Completions, ctx: &CompletionContext match &ctx.ident_ctx { IdentContext::Name(NameContext { kind: NameKind::RecordField, .. }) | IdentContext::NameRef(NameRefContext { - path_ctx: - Some(PathCompletionCtx { + kind: + Some(NameRefKind::Path(PathCompletionCtx { has_macro_bang: false, is_absolute_path: false, qualifier: None, parent: None, - kind: PathKind::Type { in_tuple_struct: true }, + kind: PathKind::Type { location: TypeLocation::TupleField }, has_type_args: false, .. - }), + })), .. }) => { if ctx.qualifier_ctx.vis_node.is_none() { diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 901f7519d25c..6266bcef34e0 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -8,8 +8,10 @@ use itertools::Itertools; use syntax::{AstNode, SyntaxNode, T}; use crate::{ - context::{CompletionContext, NameRefContext, PathCompletionCtx, PathKind, PatternContext}, - patterns::ImmediateLocation, + context::{ + CompletionContext, NameRefContext, NameRefKind, PathCompletionCtx, PathKind, + PatternContext, TypeLocation, + }, render::{render_resolution_with_import, RenderContext}, }; @@ -111,19 +113,20 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) return None; } let path_kind = match ctx.nameref_ctx() { - Some(NameRefContext { path_ctx: Some(PathCompletionCtx { kind, .. }), .. }) - if matches!( - kind, - PathKind::Expr { .. } - | PathKind::Type { .. } - | PathKind::Attr { .. } - | PathKind::Derive - | PathKind::Pat - ) => - { - Some(kind) - } - Some(NameRefContext { dot_access: Some(_), .. }) => None, + Some(NameRefContext { + kind: + Some(NameRefKind::Path(PathCompletionCtx { + kind: + kind @ (PathKind::Expr { .. } + | PathKind::Type { .. } + | PathKind::Attr { .. } + | PathKind::Derive + | PathKind::Pat), + .. + })), + .. + }) => Some(kind), + Some(NameRefContext { kind: Some(NameRefKind::DotAccess(_)), .. }) => None, None if matches!(ctx.pattern_ctx, Some(PatternContext { record_pat: None, .. })) => { Some(&PathKind::Pat) } @@ -173,8 +176,8 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) (PathKind::Pat, ItemInNs::Types(_)) => true, (PathKind::Pat, ItemInNs::Values(def)) => matches!(def, hir::ModuleDef::Const(_)), - (PathKind::Type { .. }, ItemInNs::Types(ty)) => { - if matches!(ctx.completion_location, Some(ImmediateLocation::TypeBound)) { + (PathKind::Type { location }, ItemInNs::Types(ty)) => { + if matches!(location, TypeLocation::TypeBound) { matches!(ty, ModuleDef::Trait(_)) } else { true 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 56f656f47946..846d5f090287 100644 --- a/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -44,8 +44,8 @@ use text_edit::TextEdit; use crate::{ context::{ - IdentContext, ItemListKind, NameContext, NameKind, NameRefContext, PathCompletionCtx, - PathKind, + IdentContext, ItemListKind, NameContext, NameKind, NameRefContext, NameRefKind, + PathCompletionCtx, PathKind, }, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, Completions, }; @@ -106,14 +106,13 @@ fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, Text } IdentContext::NameRef(NameRefContext { nameref, - path_ctx: - Some( + kind: + Some(NameRefKind::Path( path_ctx @ PathCompletionCtx { kind: PathKind::Item { kind: ItemListKind::TraitImpl }, .. }, - ), - .. + )), }) if path_ctx.is_trivial_path() => Some(( ImplCompletionKind::All, match nameref { diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs index 65fa1191781e..2e266b7714c8 100644 --- a/crates/ide-completion/src/completions/keyword.rs +++ b/crates/ide-completion/src/completions/keyword.rs @@ -4,11 +4,14 @@ use syntax::ast::Item; -use crate::{context::NameRefContext, CompletionContext, Completions}; +use crate::{ + context::{NameRefContext, NameRefKind}, + CompletionContext, Completions, +}; pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { let item = match ctx.nameref_ctx() { - Some(NameRefContext { keyword: Some(item), record_expr: None, .. }) => item, + Some(NameRefContext { kind: Some(NameRefKind::Keyword(item)), .. }) => item, _ => return, }; diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index 4868225ce35c..888b8f34884f 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -13,7 +13,7 @@ use text_edit::TextEdit; use crate::{ completions::postfix::format_like::add_format_like_completions, - context::{CompletionContext, DotAccess, DotAccessKind, NameRefContext}, + context::{CompletionContext, DotAccess, DotAccessKind, NameRefContext, NameRefKind}, item::{Builder, CompletionRelevancePostfixMatch}, CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope, }; @@ -25,7 +25,13 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match ctx.nameref_ctx() { Some(NameRefContext { - dot_access: Some(DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. }), + kind: + Some(NameRefKind::DotAccess(DotAccess { + receiver_ty: Some(ty), + receiver: Some(it), + kind, + .. + })), .. }) => ( it, diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs index 65805dba1ce9..d74ae260b5df 100644 --- a/crates/ide-completion/src/completions/record.rs +++ b/crates/ide-completion/src/completions/record.rs @@ -3,7 +3,7 @@ use ide_db::SymbolKind; use syntax::{ast::Expr, T}; use crate::{ - context::{NameRefContext, PatternContext}, + context::{NameRefContext, NameRefKind, PathCompletionCtx, PathKind, PatternContext}, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch, Completions, }; @@ -13,8 +13,18 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> &ctx.pattern_ctx { ctx.sema.record_pattern_missing_fields(record_pat) - } else if let Some(NameRefContext { record_expr: Some((record_expr, _)), .. }) = - ctx.nameref_ctx() + } else if let Some(NameRefContext { + kind: + Some( + NameRefKind::RecordExpr(record_expr) + | NameRefKind::Path(PathCompletionCtx { + kind: PathKind::Expr { is_func_update: Some(record_expr), .. }, + qualifier: None, + .. + }), + ), + .. + }) = ctx.nameref_ctx() { let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone())); @@ -39,7 +49,7 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> ty.original.impls_trait(ctx.db, default_trait, &[]) }); - if impl_default_trait && !missing_fields.is_empty() && ctx.path_qual().is_none() { + if impl_default_trait && !missing_fields.is_empty() { let completion_text = "..Default::default()"; let mut item = CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text); diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index 9cf0b87ad6f9..9d3a1c24293d 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -5,8 +5,7 @@ use ide_db::FxHashSet; use syntax::{ast, AstNode}; use crate::{ - context::{PathCompletionCtx, PathKind, PathQualifierCtx}, - patterns::{ImmediateLocation, TypeAnnotation}, + context::{PathCompletionCtx, PathKind, PathQualifierCtx, TypeAscriptionTarget, TypeLocation}, render::render_type_inference, CompletionContext, Completions, }; @@ -14,13 +13,13 @@ use crate::{ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) { let _p = profile::span("complete_type_path"); - let (&is_absolute_path, qualifier) = match ctx.path_context() { + let (&is_absolute_path, location, qualifier) = match ctx.path_context() { Some(PathCompletionCtx { - kind: PathKind::Type { .. }, + kind: PathKind::Type { location }, is_absolute_path, qualifier, .. - }) => (is_absolute_path, qualifier), + }) => (is_absolute_path, location, qualifier), _ => return, }; @@ -32,7 +31,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) ScopeDef::ModuleDef(Function(_) | Variant(_) | Static(_)) | ScopeDef::Local(_) => false, // unless its a constant in a generic arg list position ScopeDef::ModuleDef(Const(_)) | ScopeDef::GenericParam(ConstParam(_)) => { - ctx.expects_generic_arg() + matches!(location, TypeLocation::GenericArgList(_)) } ScopeDef::ImplSelfType(_) => { !ctx.previous_token_is(syntax::T![impl]) && !ctx.previous_token_is(syntax::T![for]) @@ -47,6 +46,14 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) } }; + let add_assoc_item = |acc: &mut Completions, item| match item { + hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArgList(_)) => { + acc.add_const(ctx, ct) + } + hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (), + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + }; + match qualifier { Some(PathQualifierCtx { is_infer_qualifier, resolution, .. }) => { if *is_infer_qualifier { @@ -54,7 +61,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) .0 .into_iter() .flat_map(|it| hir::Trait::from(it).items(ctx.sema.db)) - .for_each(|item| add_assoc_item(acc, ctx, item)); + .for_each(|item| add_assoc_item(acc, item)); return; } let resolution = match resolution { @@ -98,7 +105,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) Some(ctx.module), None, |item| { - add_assoc_item(acc, ctx, item); + add_assoc_item(acc, item); None::<()> }, ); @@ -114,7 +121,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => { // Handles `Trait::assoc` as well as `::assoc`. for item in t.items(ctx.db) { - add_assoc_item(acc, ctx, item); + add_assoc_item(acc, item); } } hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => { @@ -135,7 +142,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) // We might iterate candidates of a trait multiple times here, so deduplicate // them. if seen.insert(item) { - add_assoc_item(acc, ctx, item); + add_assoc_item(acc, item); } None::<()> }, @@ -147,7 +154,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) None if is_absolute_path => acc.add_crate_roots(ctx), None => { acc.add_nameref_keywords_with_colon(ctx); - if let Some(ImmediateLocation::TypeBound) = &ctx.completion_location { + if let TypeLocation::TypeBound = location { ctx.process_all_names(&mut |name, res| { let add_resolution = match res { ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db), @@ -162,7 +169,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) }); return; } - if let Some(ImmediateLocation::GenericArgList(arg_list)) = &ctx.completion_location { + if let TypeLocation::GenericArgList(Some(arg_list)) = location { if let Some(path_seg) = arg_list.syntax().parent().and_then(ast::PathSegment::cast) { if path_seg.syntax().ancestors().find_map(ast::TypeBound::cast).is_some() { @@ -189,25 +196,25 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) } pub(crate) fn complete_inferred_type(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - use TypeAnnotation::*; - let pat = match &ctx.completion_location { - Some(ImmediateLocation::TypeAnnotation(t)) => t, + let pat = match ctx.path_context() { + Some( + ctx @ PathCompletionCtx { + kind: PathKind::Type { location: TypeLocation::TypeAscription(ascription), .. }, + .. + }, + ) if ctx.is_trivial_path() => ascription, _ => return None, }; let x = match pat { - Let(pat) | FnParam(pat) => ctx.sema.type_of_pat(pat.as_ref()?), - Const(exp) | RetType(exp) => ctx.sema.type_of_expr(exp.as_ref()?), + TypeAscriptionTarget::Let(pat) | TypeAscriptionTarget::FnParam(pat) => { + ctx.sema.type_of_pat(pat.as_ref()?) + } + TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType(exp) => { + ctx.sema.type_of_expr(exp.as_ref()?) + } }? .adjusted(); let ty_string = x.display_source_code(ctx.db, ctx.module.into()).ok()?; acc.add(render_type_inference(ty_string, ctx)); None } - -fn add_assoc_item(acc: &mut Completions, ctx: &CompletionContext, item: hir::AssocItem) { - match item { - hir::AssocItem::Const(ct) if ctx.expects_generic_arg() => acc.add_const(ctx, ct), - hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (), - hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), - } -} diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs index dc7a342dce2a..5d062098d7d6 100644 --- a/crates/ide-completion/src/completions/use_.rs +++ b/crates/ide-completion/src/completions/use_.rs @@ -5,7 +5,10 @@ use ide_db::{FxHashSet, SymbolKind}; use syntax::{ast, AstNode}; use crate::{ - context::{CompletionContext, NameRefContext, PathCompletionCtx, PathKind, PathQualifierCtx}, + context::{ + CompletionContext, NameRefContext, NameRefKind, PathCompletionCtx, PathKind, + PathQualifierCtx, + }, item::Builder, CompletionItem, CompletionItemKind, CompletionRelevance, Completions, }; @@ -13,8 +16,13 @@ use crate::{ pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) { let (&is_absolute_path, qualifier, name_ref) = match ctx.nameref_ctx() { Some(NameRefContext { - path_ctx: - Some(PathCompletionCtx { kind: PathKind::Use, is_absolute_path, qualifier, .. }), + kind: + Some(NameRefKind::Path(PathCompletionCtx { + kind: PathKind::Use, + is_absolute_path, + qualifier, + .. + })), nameref, .. }) => (is_absolute_path, qualifier, nameref), diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 02307def9e6e..7421a3e98bf0 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -22,10 +22,7 @@ use syntax::{ use text_edit::Indel; use crate::{ - patterns::{ - determine_location, is_in_loop_body, is_in_token_of_for_loop, previous_token, - ImmediateLocation, - }, + patterns::{is_in_loop_body, is_in_token_of_for_loop, previous_token}, CompletionConfig, }; @@ -43,43 +40,7 @@ pub(crate) enum Visible { No, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub(super) enum PathKind { - Expr { - in_block_expr: bool, - in_loop_body: bool, - after_if_expr: bool, - ref_expr_parent: Option, - }, - Type { - in_tuple_struct: bool, - }, - Attr { - kind: AttrKind, - annotated_item_kind: Option, - }, - Derive, - /// Path in item position, that is inside an (Assoc)ItemList - Item { - kind: ItemListKind, - }, - Pat, - Vis { - has_in_token: bool, - }, - Use, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(super) enum ItemListKind { - SourceFile, - Module, - Impl, - TraitImpl, - Trait, - ExternBlock, -} - +/// Existing qualifiers for the thing we are currently completing. #[derive(Debug, Default)] pub(super) struct QualifierCtx { pub(super) unsafe_tok: Option, @@ -92,6 +53,7 @@ impl QualifierCtx { } } +/// The state of the path we are currently completing. #[derive(Debug)] pub(crate) struct PathCompletionCtx { /// If this is a call with () already there (or {} in case of record patterns) @@ -126,6 +88,65 @@ impl PathCompletionCtx { } } +/// The kind of path we are completing right now. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) enum PathKind { + Expr { + in_block_expr: bool, + in_loop_body: bool, + after_if_expr: bool, + ref_expr_parent: Option, + is_func_update: Option, + }, + Type { + location: TypeLocation, + }, + Attr { + kind: AttrKind, + annotated_item_kind: Option, + }, + Derive, + /// Path in item position, that is inside an (Assoc)ItemList + Item { + kind: ItemListKind, + }, + Pat, + Vis { + has_in_token: bool, + }, + Use, +} + +/// Original file ast nodes +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum TypeLocation { + TupleField, + TypeAscription(TypeAscriptionTarget), + GenericArgList(Option), + TypeBound, + Other, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum TypeAscriptionTarget { + Let(Option), + FnParam(Option), + RetType(Option), + Const(Option), +} + +/// The kind of item list a [`PathKind::Item`] belongs to. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(super) enum ItemListKind { + SourceFile, + Module, + Impl, + TraitImpl, + Trait, + ExternBlock, +} + +/// The path qualifier state of the path we are completing. #[derive(Debug)] pub(crate) struct PathQualifierCtx { pub(crate) path: ast::Path, @@ -138,6 +159,7 @@ pub(crate) struct PathQualifierCtx { pub(crate) is_infer_qualifier: bool, } +/// The state of the pattern we are completing. #[derive(Debug)] pub(super) struct PatternContext { pub(super) refutability: PatternRefutability, @@ -150,12 +172,14 @@ pub(super) struct PatternContext { pub(super) record_pat: Option, } +/// The state of the lifetime we are completing. #[derive(Debug)] pub(super) struct LifetimeContext { pub(super) lifetime: Option, pub(super) kind: LifetimeKind, } +/// The kind of lifetime we are completing. #[derive(Debug)] pub(super) enum LifetimeKind { LifetimeParam { is_decl: bool, param: ast::LifetimeParam }, @@ -164,6 +188,7 @@ pub(super) enum LifetimeKind { LabelDef, } +/// The state of the name we are completing. #[derive(Debug)] pub(super) struct NameContext { #[allow(dead_code)] @@ -171,6 +196,7 @@ pub(super) struct NameContext { pub(super) kind: NameKind, } +/// The kind of the name we are completing. #[derive(Debug)] #[allow(dead_code)] pub(super) enum NameKind { @@ -195,34 +221,46 @@ pub(super) enum NameKind { Variant, } +/// The state of the NameRef we are completing. #[derive(Debug)] pub(super) struct NameRefContext { /// NameRef syntax in the original file pub(super) nameref: Option, - // FIXME: these fields are actually disjoint -> enum - pub(super) dot_access: Option, - pub(super) path_ctx: Option, - /// Position where we are only interested in keyword completions - pub(super) keyword: Option, - /// The record expression this nameref is a field of - pub(super) record_expr: Option<(ast::RecordExpr, bool)>, + // FIXME: This shouldn't be an Option + pub(super) kind: Option, } +/// The kind of the NameRef we are completing. +#[derive(Debug)] +pub(super) enum NameRefKind { + Path(PathCompletionCtx), + DotAccess(DotAccess), + /// Position where we are only interested in keyword completions + Keyword(ast::Item), + /// The record expression this nameref is a field of + RecordExpr(ast::RecordExpr), +} + +/// The identifier we are currently completing. #[derive(Debug)] pub(super) enum IdentContext { Name(NameContext), NameRef(NameRefContext), Lifetime(LifetimeContext), - /// Original token, fake token + /// The string the cursor is currently inside String { + /// original token original: ast::String, + /// fake token expanded: Option, }, + /// Set if we are currently completing in an unexpanded attribute, this usually implies a builtin attribute like `allow($0)` UnexpandedAttrTT { fake_attribute_under_caret: Option, }, } +/// Information about the field or method access we are completing. #[derive(Debug)] pub(super) struct DotAccess { pub(super) receiver: Option, @@ -278,9 +316,9 @@ pub(crate) struct CompletionContext<'a> { /// The parent impl of the cursor position if it exists. 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, - pub(super) completion_location: Option, pub(super) previous_token: Option, pub(super) ident_ctx: IdentContext, @@ -341,9 +379,10 @@ impl<'a> CompletionContext<'a> { pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> { match self.nameref_ctx() { - Some(NameRefContext { dot_access: Some(DotAccess { receiver, .. }), .. }) => { - receiver.as_ref() - } + Some(NameRefContext { + kind: Some(NameRefKind::DotAccess(DotAccess { receiver, .. })), + .. + }) => receiver.as_ref(), _ => None, } } @@ -352,13 +391,11 @@ impl<'a> CompletionContext<'a> { self.dot_receiver().is_some() } - // FIXME: This shouldn't exist - pub(crate) fn expects_generic_arg(&self) -> bool { - matches!(self.completion_location, Some(ImmediateLocation::GenericArgList(_))) - } - pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> { - self.nameref_ctx().and_then(|ctx| ctx.path_ctx.as_ref()) + self.nameref_ctx().and_then(|ctx| match &ctx.kind { + Some(NameRefKind::Path(path)) => Some(path), + _ => None, + }) } pub(crate) fn path_qual(&self) -> Option<&ast::Path> { @@ -505,7 +542,6 @@ impl<'a> CompletionContext<'a> { function_def: None, impl_def: None, incomplete_let: false, - completion_location: None, previous_token: None, // dummy value, will be overwritten ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None }, @@ -857,7 +893,7 @@ impl<'a> CompletionContext<'a> { let parent = name_ref.syntax().parent()?; let (mut nameref_ctx, _, _) = Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); - if let Some(path_ctx) = &mut nameref_ctx.path_ctx { + if let Some(NameRefKind::Path(path_ctx)) = &mut nameref_ctx.kind { path_ctx.kind = PathKind::Derive; } self.ident_ctx = IdentContext::NameRef(nameref_ctx); @@ -898,8 +934,6 @@ impl<'a> CompletionContext<'a> { return Some(()); } }; - self.completion_location = - determine_location(&self.sema, original_file, offset, &name_like); self.impl_def = self .sema .token_ancestors_with_macros(self.token.clone()) @@ -1026,23 +1060,13 @@ impl<'a> CompletionContext<'a> { ) -> (NameRefContext, Option, QualifierCtx) { let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); - let mut res = ( - NameRefContext { - dot_access: None, - path_ctx: None, - nameref, - record_expr: None, - keyword: None, - }, - None, - QualifierCtx::default(), - ); + let mut res = (NameRefContext { nameref, kind: None }, None, QualifierCtx::default()); let (nameref_ctx, pattern_ctx, qualifier_ctx) = &mut res; if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { - nameref_ctx.record_expr = + nameref_ctx.kind = find_node_in_file_compensated(original_file, &record_field.parent_record_lit()) - .zip(Some(false)); + .map(NameRefKind::RecordExpr); return res; } if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { @@ -1067,7 +1091,7 @@ impl<'a> CompletionContext<'a> { match parent { ast::PathSegment(segment) => segment, ast::FieldExpr(field) => { - let receiver = find_in_original_file(field.expr(), original_file); + let receiver = find_opt_node_in_file(original_file, field.expr()); let receiver_is_ambiguous_float_literal = match &receiver { Some(ast::Expr::Literal(l)) => matches! { l.kind(), @@ -1075,20 +1099,20 @@ impl<'a> CompletionContext<'a> { }, _ => false, }; - nameref_ctx.dot_access = Some(DotAccess { + nameref_ctx.kind = Some(NameRefKind::DotAccess(DotAccess { receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, receiver - }); + })); return res; }, ast::MethodCallExpr(method) => { - let receiver = find_in_original_file(method.receiver(), original_file); - nameref_ctx.dot_access = Some(DotAccess { + let receiver = find_opt_node_in_file(original_file, method.receiver()); + nameref_ctx.kind = Some(NameRefKind::DotAccess(DotAccess { receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) }, receiver - }); + })); return res; }, _ => return res, @@ -1113,10 +1137,11 @@ impl<'a> CompletionContext<'a> { }) .unwrap_or(false) }; - let mut fill_record_expr = |syn: &SyntaxNode| { + let func_update_record = |syn: &SyntaxNode| { if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) { - nameref_ctx.record_expr = - find_node_in_file_compensated(original_file, &record_expr).zip(Some(true)); + find_node_in_file_compensated(original_file, &record_expr) + } else { + None } }; let after_if_expr = |node: SyntaxNode| { @@ -1161,33 +1186,91 @@ impl<'a> CompletionContext<'a> { None }; + let type_location = |it: Option| { + let parent = it?; + let res = match_ast! { + match parent { + ast::Const(it) => { + let name = find_opt_node_in_file(original_file, it.name())?; + let original = ast::Const::cast(name.syntax().parent()?)?; + TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body())) + }, + ast::RetType(it) => { + if it.thin_arrow_token().is_none() { + return None; + } + let parent = match ast::Fn::cast(parent.parent()?) { + Some(x) => x.param_list(), + None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(), + }; + + let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?; + TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! { + match parent { + ast::ClosureExpr(it) => { + it.body() + }, + ast::Fn(it) => { + it.body().map(ast::Expr::BlockExpr) + }, + _ => return None, + } + })) + }, + ast::Param(it) => { + if it.colon_token().is_none() { + return None; + } + TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat()))) + }, + ast::LetStmt(it) => { + if it.colon_token().is_none() { + return None; + } + TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat()))) + }, + 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))), + // is this case needed? + ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(original_file, Some(it))), + ast::TupleField(_) => TypeLocation::TupleField, + _ => return None, + } + }; + Some(res) + }; + // Infer the path kind let kind = path.syntax().parent().and_then(|it| { match_ast! { match it { - ast::PathType(it) => Some(PathKind::Type { - in_tuple_struct: it.syntax().parent().map_or(false, |it| ast::TupleField::can_cast(it.kind())) - }), + ast::PathType(it) => { + let location = type_location(it.syntax().parent()); + Some(PathKind::Type { + location: location.unwrap_or(TypeLocation::Other), + }) + }, ast::PathExpr(it) => { if let Some(p) = it.syntax().parent() { if ast::ExprStmt::can_cast(p.kind()) { if let Some(kind) = inbetween_body_and_decl_check(p) { - nameref_ctx.keyword = Some(kind); + nameref_ctx.kind = Some(NameRefKind::Keyword(kind)); return None; } } } - fill_record_expr(it.syntax()); - 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 }) + Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent, is_func_update }) }, ast::TupleStructPat(it) => { path_ctx.has_call_parens = true; @@ -1205,7 +1288,7 @@ impl<'a> CompletionContext<'a> { }, ast::MacroCall(it) => { if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) { - nameref_ctx.keyword = Some(kind); + nameref_ctx.kind = Some(NameRefKind::Keyword(kind)); return None; } @@ -1213,7 +1296,12 @@ impl<'a> CompletionContext<'a> { let parent = it.syntax().parent(); match parent.as_ref().map(|it| it.kind()) { Some(SyntaxKind::MACRO_PAT) => Some(PathKind::Pat), - Some(SyntaxKind::MACRO_TYPE) => Some(PathKind::Type { in_tuple_struct: false }), + 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! { @@ -1236,10 +1324,10 @@ impl<'a> CompletionContext<'a> { 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()); - fill_record_expr(it.syntax()); let ref_expr_parent = path.as_single_name_ref() .and_then(|_| it.syntax().parent()).and_then(ast::RefExpr::cast); - PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent } + 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 } }); }, } @@ -1365,7 +1453,7 @@ impl<'a> CompletionContext<'a> { } } } - nameref_ctx.path_ctx = Some(path_ctx); + nameref_ctx.kind = Some(NameRefKind::Path(path_ctx)); res } } @@ -1422,15 +1510,14 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont } } -fn find_in_original_file(x: Option, original_file: &SyntaxNode) -> Option { - fn find_node_with_range(syntax: &SyntaxNode, range: TextRange) -> Option { - let range = syntax.text_range().intersect(range)?; - syntax.covering_element(range).ancestors().find_map(N::cast) - } - x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r)) +/// Attempts to find `node` inside `syntax` via `node`'s text range. +/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead. +fn find_opt_node_in_file(syntax: &SyntaxNode, node: Option) -> Option { + find_node_in_file(syntax, &node?) } /// Attempts to find `node` inside `syntax` via `node`'s text range. +/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead. fn find_node_in_file(syntax: &SyntaxNode, node: &N) -> Option { let syntax_range = syntax.text_range(); let range = node.syntax().text_range(); @@ -1449,11 +1536,21 @@ fn find_node_in_file_compensated(syntax: &SyntaxNode, node: &N) -> O return None; } let range = TextRange::new(range.start(), end); - // our inserted ident could cause `range` to be go outside of the original syntax, so cap it + // 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) } +/// 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( + syntax: &SyntaxNode, + node: Option, +) -> Option { + find_node_in_file_compensated(syntax, &node?) +} + fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> { if let Some(qual) = path.qualifier() { return Some((qual, false)); diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs index bc46ff7f2d75..9efb42c4de75 100644 --- a/crates/ide-completion/src/patterns.rs +++ b/crates/ide-completion/src/patterns.rs @@ -4,171 +4,16 @@ //! This means we for example expand a NameRef token to its outermost Path node, as semantically these act in the same location //! and the completions usually query for path specific things on the Path context instead. This simplifies some location handling. -use hir::Semantics; -use ide_db::RootDatabase; use syntax::{ - ast::{self, HasLoopBody, HasName}, + ast::{self, HasLoopBody}, match_ast, AstNode, SyntaxElement, SyntaxKind::*, - SyntaxNode, SyntaxToken, TextRange, TextSize, + SyntaxNode, SyntaxToken, }; #[cfg(test)] use crate::tests::check_pattern_is_applicable; -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum TypeAnnotation { - Let(Option), - FnParam(Option), - RetType(Option), - Const(Option), -} - -/// Direct parent "thing" of what we are currently completing. -/// -/// This may contain nodes of the fake file as well as the original, comments on the variants specify -/// from which file the nodes are. -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum ImmediateLocation { - TypeBound, - /// Original file ast node - TypeAnnotation(TypeAnnotation), - // Only set from a type arg - /// Original file ast node - GenericArgList(ast::GenericArgList), -} - -pub(crate) fn determine_location( - sema: &Semantics, - original_file: &SyntaxNode, - offset: TextSize, - name_like: &ast::NameLike, -) -> Option { - let node = match name_like { - ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref), - ast::NameLike::Name(name) => name.syntax().clone(), - ast::NameLike::Lifetime(lt) => lt.syntax().clone(), - }; - - match_ast! { - match node { - ast::TypeBoundList(_it) => return Some(ImmediateLocation::TypeBound), - _ => (), - } - }; - - let parent = match node.parent() { - Some(parent) => match ast::MacroCall::cast(parent.clone()) { - // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. - // This is usually fine as the node expansion code above already accounts for that with - // the ancestors call, but there is one exception to this which is that when an attribute - // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ. - // FIXME path expr and statement have a similar problem - Some(call) - if call.excl_token().is_none() - && call.token_tree().is_none() - && call.semicolon_token().is_none() => - { - call.syntax().parent()? - } - _ => parent, - }, - // SourceFile - None => return None, - }; - - let res = match_ast! { - match parent { - ast::TypeBound(_) => ImmediateLocation::TypeBound, - ast::TypeBoundList(_) => ImmediateLocation::TypeBound, - ast::GenericArgList(_) => sema - .find_node_at_offset_with_macros(original_file, offset) - .map(ImmediateLocation::GenericArgList)?, - ast::Const(it) => { - if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) { - return None; - } - let name = find_in_original_file(it.name(), original_file)?; - let original = ast::Const::cast(name.syntax().parent()?)?; - ImmediateLocation::TypeAnnotation(TypeAnnotation::Const(original.body())) - }, - ast::RetType(it) => { - if it.thin_arrow_token().is_none() { - return None; - } - if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) { - return None; - } - let parent = match ast::Fn::cast(parent.parent()?) { - Some(x) => x.param_list(), - None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(), - }; - let parent = find_in_original_file(parent, original_file)?.syntax().parent()?; - ImmediateLocation::TypeAnnotation(TypeAnnotation::RetType(match_ast! { - match parent { - ast::ClosureExpr(it) => { - it.body() - }, - ast::Fn(it) => { - it.body().map(ast::Expr::BlockExpr) - }, - _ => return None, - } - })) - }, - ast::Param(it) => { - if it.colon_token().is_none() { - return None; - } - if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) { - return None; - } - ImmediateLocation::TypeAnnotation(TypeAnnotation::FnParam(find_in_original_file(it.pat(), original_file))) - }, - ast::LetStmt(it) => { - if it.colon_token().is_none() { - return None; - } - if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) { - return None; - } - ImmediateLocation::TypeAnnotation(TypeAnnotation::Let(find_in_original_file(it.pat(), original_file))) - }, - _ => return None, - } - }; - fn find_in_original_file(x: Option, original_file: &SyntaxNode) -> Option { - x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r)) - } - Some(res) -} - -/// Maximize a nameref to its enclosing path if its the last segment of said path. -/// That is, when completing a [`NameRef`] we actually handle it as the path it is part of when determining -/// its location. -fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode { - if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { - let p = segment.parent_path(); - if p.parent_path().is_none() { - // Get rid of PathExpr, PathType, etc... - let path = p - .syntax() - .ancestors() - .take_while(|it| it.text_range() == p.syntax().text_range()) - .last(); - if let Some(it) = path { - return it; - } - } - } - name_ref.syntax().clone() -} - -fn find_node_with_range(syntax: &SyntaxNode, range: TextRange) -> Option { - let range = syntax.text_range().intersect(range)?; - syntax.covering_element(range).ancestors().find_map(N::cast) -} - pub(crate) fn previous_token(element: SyntaxElement) -> Option { element.into_token().and_then(previous_non_trivia_token) } diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index 0be51b0e3ff6..566eaa575d49 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -8,7 +8,8 @@ use syntax::SmolStr; use crate::{ context::{ - CompletionContext, DotAccess, DotAccessKind, NameRefContext, PathCompletionCtx, PathKind, + CompletionContext, DotAccess, DotAccessKind, NameRefContext, NameRefKind, + PathCompletionCtx, PathKind, }, item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance}, render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext}, @@ -212,7 +213,10 @@ fn should_add_parens(ctx: &CompletionContext) -> bool { if matches!( ctx.nameref_ctx(), Some(NameRefContext { - dot_access: Some(DotAccess { kind: DotAccessKind::Method { has_parens: true }, .. }), + kind: Some(NameRefKind::DotAccess(DotAccess { + kind: DotAccessKind::Method { has_parens: true }, + .. + })), .. }) ) { diff --git a/crates/ide-completion/src/tests/record.rs b/crates/ide-completion/src/tests/record.rs index 9369034cc629..c7514e1b5787 100644 --- a/crates/ide-completion/src/tests/record.rs +++ b/crates/ide-completion/src/tests/record.rs @@ -105,7 +105,6 @@ fn foo(f: Struct) { #[test] fn functional_update() { // FIXME: This should filter out all completions that do not have the type `Foo` - // FIXME: Fields should not show up after `.` check( r#" //- minicore:default @@ -192,8 +191,6 @@ fn main() { } "#, expect![[r#" - fd foo1 u32 - fd foo2 u32 fn default() (as Default) fn() -> Self "#]], );