diff --git a/crates/ide-completion/src/completions/field.rs b/crates/ide-completion/src/completions/field.rs index bddfc1de1a..e2eed52dfa 100644 --- a/crates/ide-completion/src/completions/field.rs +++ b/crates/ide-completion/src/completions/field.rs @@ -18,7 +18,7 @@ pub(crate) fn complete_field_list(acc: &mut Completions, ctx: &CompletionContext is_absolute_path: false, qualifier: None, parent: None, - kind: PathKind::Type { in_tuple_struct: true }, + kind: PathKind::Type { in_tuple_struct: true, ascription: None }, has_type_args: false, .. })), diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index 9cf0b87ad6..0e1cccb183 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -5,8 +5,8 @@ use ide_db::FxHashSet; use syntax::{ast, AstNode}; use crate::{ - context::{PathCompletionCtx, PathKind, PathQualifierCtx}, - patterns::{ImmediateLocation, TypeAnnotation}, + context::{PathCompletionCtx, PathKind, PathQualifierCtx, TypeAscriptionTarget}, + patterns::ImmediateLocation, render::render_type_inference, CompletionContext, Completions, }; @@ -189,14 +189,22 @@ 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 dbg!(ctx.path_context()) { + Some( + ctx @ PathCompletionCtx { + kind: PathKind::Type { ascription: Some(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()?; diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index f790aa56eb..b298c372b3 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -43,44 +43,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, - is_func_update: 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, @@ -93,6 +56,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) @@ -127,6 +91,58 @@ 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 { + in_tuple_struct: bool, + /// Whether this type path is a type ascription or not + /// Original file ast node + ascription: Option, + }, + 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(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, @@ -139,6 +155,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, @@ -151,12 +168,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 }, @@ -165,6 +184,7 @@ pub(super) enum LifetimeKind { LabelDef, } +/// The state of the name we are completing. #[derive(Debug)] pub(super) struct NameContext { #[allow(dead_code)] @@ -172,6 +192,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 { @@ -196,6 +217,7 @@ 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 @@ -203,6 +225,7 @@ pub(super) struct NameRefContext { pub(super) kind: Option, } +/// The kind of the NameRef we are completing. #[derive(Debug)] pub(super) enum NameRefKind { Path(PathCompletionCtx), @@ -213,21 +236,26 @@ pub(super) enum NameRefKind { 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, @@ -1161,13 +1189,65 @@ impl<'a> CompletionContext<'a> { None }; + let fetch_ascription = |it: Option| { + let parent = it?; + match_ast! { + match parent { + ast::Const(it) => { + let name = find_in_original_file(it.name(), original_file)?; + let original = ast::Const::cast(name.syntax().parent()?)?; + Some(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_in_original_file(parent, original_file)?.syntax().parent()?; + Some(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; + } + Some(TypeAscriptionTarget::FnParam(find_in_original_file(it.pat(), original_file))) + }, + ast::LetStmt(it) => { + if it.colon_token().is_none() { + return None; + } + Some(TypeAscriptionTarget::Let(find_in_original_file(it.pat(), original_file))) + }, + _ => None, + } + } + }; + // 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 ascription = fetch_ascription(it.syntax().parent()); + Some(PathKind::Type { + in_tuple_struct: it.syntax().parent().map_or(false, |it| ast::TupleField::can_cast(it.kind())), + ascription, + }) + }, ast::PathExpr(it) => { if let Some(p) = it.syntax().parent() { if ast::ExprStmt::can_cast(p.kind()) { @@ -1178,7 +1258,7 @@ impl<'a> CompletionContext<'a> { } } - path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); + 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()); @@ -1212,7 +1292,7 @@ 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) => Some(PathKind::Type { in_tuple_struct: false, ascription: None }), 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! { diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs index bc46ff7f2d..88cb486f5d 100644 --- a/crates/ide-completion/src/patterns.rs +++ b/crates/ide-completion/src/patterns.rs @@ -7,23 +7,15 @@ 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, TextSize, }; #[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 @@ -31,8 +23,6 @@ pub(crate) enum TypeAnnotation { #[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), @@ -84,62 +74,9 @@ pub(crate) fn determine_location( 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) } @@ -164,11 +101,6 @@ fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode { 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) }