diff options
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-completion/src/completions')
7 files changed, 186 insertions, 106 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs index cf40ca489..02004ff7b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs @@ -19,7 +19,7 @@ pub(crate) fn complete_dot( }; // Suggest .await syntax for types that implement Future trait - if receiver_ty.impls_future(ctx.db) { + if receiver_ty.impls_into_future(ctx.db) { let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), "await"); item.detail("expr.await"); diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs index 5d0ddaaf2..588b52cc1 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs @@ -1,8 +1,10 @@ //! Completion of names from the current scope in expression position. use hir::ScopeDef; +use syntax::ast; use crate::{ + completions::record::add_default_update, context::{ExprCtx, PathCompletionCtx, Qualified}, CompletionContext, Completions, }; @@ -219,60 +221,90 @@ pub(crate) fn complete_expr_path( _ => (), }); - if is_func_update.is_none() { - let mut add_keyword = - |kw, snippet| acc.add_keyword_snippet_expr(ctx, incomplete_let, kw, snippet); + match is_func_update { + Some(record_expr) => { + let ty = ctx.sema.type_of_expr(&ast::Expr::RecordExpr(record_expr.clone())); - if !in_block_expr { - add_keyword("unsafe", "unsafe {\n $0\n}"); - } - add_keyword("match", "match $1 {\n $0\n}"); - add_keyword("while", "while $1 {\n $0\n}"); - add_keyword("while let", "while let $1 = $2 {\n $0\n}"); - add_keyword("loop", "loop {\n $0\n}"); - if in_match_guard { - add_keyword("if", "if $0"); - } else { - add_keyword("if", "if $1 {\n $0\n}"); + match ty.as_ref().and_then(|t| t.original.as_adt()) { + Some(hir::Adt::Union(_)) => (), + _ => { + cov_mark::hit!(functional_update); + let missing_fields = + ctx.sema.record_literal_missing_fields(record_expr); + if !missing_fields.is_empty() { + add_default_update(acc, ctx, ty); + } + } + }; } - add_keyword("if let", "if let $1 = $2 {\n $0\n}"); - add_keyword("for", "for $1 in $2 {\n $0\n}"); - add_keyword("true", "true"); - add_keyword("false", "false"); + None => { + let mut add_keyword = |kw, snippet| { + acc.add_keyword_snippet_expr(ctx, incomplete_let, kw, snippet) + }; - if in_condition || in_block_expr { - add_keyword("let", "let"); - } + if !in_block_expr { + add_keyword("unsafe", "unsafe {\n $0\n}"); + } + add_keyword("match", "match $1 {\n $0\n}"); + add_keyword("while", "while $1 {\n $0\n}"); + add_keyword("while let", "while let $1 = $2 {\n $0\n}"); + add_keyword("loop", "loop {\n $0\n}"); + if in_match_guard { + add_keyword("if", "if $0"); + } else { + add_keyword("if", "if $1 {\n $0\n}"); + } + add_keyword("if let", "if let $1 = $2 {\n $0\n}"); + add_keyword("for", "for $1 in $2 {\n $0\n}"); + add_keyword("true", "true"); + add_keyword("false", "false"); - if after_if_expr { - add_keyword("else", "else {\n $0\n}"); - add_keyword("else if", "else if $1 {\n $0\n}"); - } + if in_condition || in_block_expr { + add_keyword("let", "let"); + } - if wants_mut_token { - add_keyword("mut", "mut "); - } + if after_if_expr { + add_keyword("else", "else {\n $0\n}"); + add_keyword("else if", "else if $1 {\n $0\n}"); + } - if in_loop_body { - if in_block_expr { - add_keyword("continue", "continue;"); - add_keyword("break", "break;"); - } else { - add_keyword("continue", "continue"); - add_keyword("break", "break"); + if wants_mut_token { + add_keyword("mut", "mut "); + } + + if in_loop_body { + if in_block_expr { + add_keyword("continue", "continue;"); + add_keyword("break", "break;"); + } else { + add_keyword("continue", "continue"); + add_keyword("break", "break"); + } } - } - if let Some(ty) = innermost_ret_ty { - add_keyword( - "return", - match (in_block_expr, ty.is_unit()) { - (true, true) => "return ;", - (true, false) => "return;", - (false, true) => "return $0", - (false, false) => "return", - }, - ); + if let Some(ret_ty) = innermost_ret_ty { + add_keyword( + "return", + match (ret_ty.is_unit(), in_block_expr) { + (true, true) => { + cov_mark::hit!(return_unit_block); + "return;" + } + (true, false) => { + cov_mark::hit!(return_unit_no_block); + "return" + } + (false, true) => { + cov_mark::hit!(return_value_block); + "return $0;" + } + (false, false) => { + cov_mark::hit!(return_value_no_block); + "return $0" + } + }, + ); + } } } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs index e9256803c..785db6fde 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -233,7 +233,8 @@ fn add_type_alias_impl( type_alias: hir::TypeAlias, ) { let alias_name = type_alias.name(ctx.db); - let (alias_name, escaped_name) = (alias_name.to_smol_str(), alias_name.escaped().to_smol_str()); + let (alias_name, escaped_name) = + (alias_name.unescaped().to_smol_str(), alias_name.to_smol_str()); let label = format!("type {} =", alias_name); let replacement = format!("type {} = ", escaped_name); diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs index 3989a451b..1d03c8cc5 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs @@ -75,16 +75,17 @@ impl Future for A {} fn foo(a: A) { a.$0 } "#, expect![[r#" - kw await expr.await - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + kw await expr.await + me into_future() (as IntoFuture) fn(self) -> <Self as IntoFuture>::IntoFuture + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr "#]], ); @@ -98,18 +99,45 @@ fn foo() { } "#, expect![[r#" - kw await expr.await - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + kw await expr.await + me into_future() (use core::future::IntoFuture) fn(self) -> <Self as IntoFuture>::IntoFuture + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr "#]], - ) + ); + } + + #[test] + fn test_completion_await_impls_into_future() { + check( + r#" +//- minicore: future +use core::future::*; +struct A {} +impl IntoFuture for A {} +fn foo(a: A) { a.$0 } +"#, + expect![[r#" + kw await expr.await + me into_future() (as IntoFuture) fn(self) -> <Self as IntoFuture>::IntoFuture + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + "#]], + ); } #[test] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs index 9c975b929..950731eb4 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs @@ -53,6 +53,7 @@ pub(crate) fn complete_mod( let existing_mod_declarations = current_module .children(ctx.db) .filter_map(|module| Some(module.name(ctx.db)?.to_string())) + .filter(|module| module != ctx.original_token.text()) .collect::<FxHashSet<_>>(); let module_declaration_file = @@ -351,4 +352,23 @@ fn ignored_bar() {} "#]], ); } + + #[test] + fn semi_colon_completion() { + check( + r#" +//- /lib.rs +mod foo; +//- /foo.rs +mod bar { + mod baz$0 +} +//- /foo/bar/baz.rs +fn baz() {} +"#, + expect![[r#" + md baz; + "#]], + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs index 6b94347e0..b273a4cb5 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs @@ -173,7 +173,7 @@ impl FormatStrParser { } } (State::Expr, ':') if chars.peek().copied() == Some(':') => { - // path seperator + // path separator current_expr.push_str("::"); chars.next(); } @@ -185,7 +185,7 @@ impl FormatStrParser { current_expr = String::new(); self.state = State::FormatOpts; } else { - // We're inside of braced expression, assume that it's a struct field name/value delimeter. + // We're inside of braced expression, assume that it's a struct field name/value delimiter. current_expr.push(chr); } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs index 1c9042390..5d96fbd30 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs @@ -3,7 +3,7 @@ use ide_db::SymbolKind; use syntax::ast::{self, Expr}; use crate::{ - context::{DotAccess, DotAccessKind, ExprCtx, PathCompletionCtx, PatternContext, Qualified}, + context::{DotAccess, DotAccessKind, PatternContext}, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch, Completions, }; @@ -14,7 +14,24 @@ pub(crate) fn complete_record_pattern_fields( pattern_ctx: &PatternContext, ) { if let PatternContext { record_pat: Some(record_pat), .. } = pattern_ctx { - complete_fields(acc, ctx, ctx.sema.record_pattern_missing_fields(record_pat)); + let ty = ctx.sema.type_of_pat(&ast::Pat::RecordPat(record_pat.clone())); + let missing_fields = match ty.as_ref().and_then(|t| t.original.as_adt()) { + Some(hir::Adt::Union(un)) => { + // ctx.sema.record_pat_missing_fields will always return + // an empty Vec on a union literal. This is normally + // reasonable, but here we'd like to present the full list + // of fields if the literal is empty. + let were_fields_specified = + record_pat.record_pat_field_list().and_then(|fl| fl.fields().next()).is_some(); + + match were_fields_specified { + false => un.fields(ctx.db).into_iter().map(|f| (f, f.ty(ctx.db))).collect(), + true => return, + } + } + _ => ctx.sema.record_pattern_missing_fields(record_pat), + }; + complete_fields(acc, ctx, missing_fields); } } @@ -42,8 +59,13 @@ pub(crate) fn complete_record_expr_fields( } _ => { let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); - add_default_update(acc, ctx, ty, &missing_fields); + + if !missing_fields.is_empty() { + cov_mark::hit!(functional_update_field); + add_default_update(acc, ctx, ty); + } if dot_prefix { + cov_mark::hit!(functional_update_one_dot); let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), ".."); item.insert_text("."); @@ -56,41 +78,18 @@ pub(crate) fn complete_record_expr_fields( complete_fields(acc, ctx, missing_fields); } -// FIXME: This should probably be part of complete_path_expr -pub(crate) fn complete_record_expr_func_update( - acc: &mut Completions, - ctx: &CompletionContext<'_>, - path_ctx: &PathCompletionCtx, - expr_ctx: &ExprCtx, -) { - if !matches!(path_ctx.qualified, Qualified::No) { - return; - } - if let ExprCtx { is_func_update: Some(record_expr), .. } = expr_ctx { - let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone())); - - match ty.as_ref().and_then(|t| t.original.as_adt()) { - Some(hir::Adt::Union(_)) => (), - _ => { - let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); - add_default_update(acc, ctx, ty, &missing_fields); - } - }; - } -} - -fn add_default_update( +pub(crate) fn add_default_update( acc: &mut Completions, ctx: &CompletionContext<'_>, ty: Option<hir::TypeInfo>, - missing_fields: &[(hir::Field, hir::Type)], ) { let default_trait = ctx.famous_defs().core_default_Default(); - let impl_default_trait = default_trait + let impls_default_trait = default_trait .zip(ty.as_ref()) .map_or(false, |(default_trait, ty)| ty.original.impls_trait(ctx.db, default_trait, &[])); - if impl_default_trait && !missing_fields.is_empty() { + if impls_default_trait { // FIXME: This should make use of scope_def like completions so we get all the other goodies + // that is we should handle this like actually completing the default function let completion_text = "..Default::default()"; let mut item = CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text); let completion_text = @@ -130,7 +129,7 @@ mod tests { #[test] fn literal_struct_completion_edit() { check_edit( - "FooDesc {…}", + "FooDesc{}", r#" struct FooDesc { pub bar: bool } @@ -155,7 +154,7 @@ fn baz() { #[test] fn literal_struct_impl_self_completion() { check_edit( - "Self {…}", + "Self{}", r#" struct Foo { bar: u64, @@ -181,7 +180,7 @@ impl Foo { ); check_edit( - "Self(…)", + "Self()", r#" mod submod { pub struct Foo(pub u64); @@ -210,7 +209,7 @@ impl submod::Foo { #[test] fn literal_struct_completion_from_sub_modules() { check_edit( - "submod::Struct {…}", + "submod::Struct{}", r#" mod submod { pub struct Struct { @@ -239,7 +238,7 @@ fn f() -> submod::Struct { #[test] fn literal_struct_complexion_module() { check_edit( - "FooDesc {…}", + "FooDesc{}", r#" mod _69latrick { pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool } |