From e73d140b51d7bd4b42cadf2dbd825b1dbc7cedb6 Mon Sep 17 00:00:00 2001 From: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> Date: Fri, 13 Nov 2020 17:17:16 +0100 Subject: [PATCH] add suggestion ..Default::default() for remaining struct fields in a constructor #6492 Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> --- Cargo.lock | 1 + crates/assists/src/utils.rs | 12 ++- crates/completion/Cargo.toml | 1 + crates/completion/src/completions/record.rs | 108 +++++++++++++++++++- 4 files changed, 118 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4940110684..715a809789 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,7 @@ dependencies = [ name = "completion" version = "0.0.0" dependencies = [ + "assists", "base_db", "expect-test", "hir", diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 7071fe96b1..3fef75d57a 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs @@ -257,6 +257,12 @@ pub mod convert { } } +pub mod default { + pub trait Default: Sized { + fn default() -> Self; + } +} + pub mod iter { pub use self::traits::{collect::IntoIterator, iterator::Iterator}; mod traits { @@ -327,7 +333,7 @@ pub mod option { } pub mod prelude { - pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}}; + pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}, default::Default}; } #[prelude_import] pub use prelude::*; @@ -345,6 +351,10 @@ pub use prelude::*; self.find_enum("core:option:Option") } + pub fn core_default_Default(&self) -> Option { + self.find_trait("core:default:Default") + } + pub fn core_iter_Iterator(&self) -> Option { self.find_trait("core:iter:traits:iterator:Iterator") } diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml index b79ee33f73..3015ec9e0e 100644 --- a/crates/completion/Cargo.toml +++ b/crates/completion/Cargo.toml @@ -14,6 +14,7 @@ itertools = "0.9.0" log = "0.4.8" rustc-hash = "1.1.0" +assists = { path = "../assists", version = "0.0.0" } stdx = { path = "../stdx", version = "0.0.0" } syntax = { path = "../syntax", version = "0.0.0" } text_edit = { path = "../text_edit", version = "0.0.0" } diff --git a/crates/completion/src/completions/record.rs b/crates/completion/src/completions/record.rs index 0f611084b0..2049b9d091 100644 --- a/crates/completion/src/completions/record.rs +++ b/crates/completion/src/completions/record.rs @@ -1,16 +1,43 @@ //! Complete fields in record literals and patterns. -use crate::{CompletionContext, Completions}; +use assists::utils::FamousDefs; +use syntax::ast::Expr; + +use crate::{ + item::CompletionKind, CompletionContext, CompletionItem, CompletionItemKind, Completions, +}; pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) { (None, None) => return None, (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat), - (_, Some(record_lit)) => ctx.sema.record_literal_missing_fields(record_lit), + (_, Some(record_lit)) => { + let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_lit.clone())); + let default_trait = FamousDefs(&ctx.sema, ctx.krate).core_default_Default(); + let impl_default_trait = default_trait + .and_then(|default_trait| ty.map(|ty| ty.impls_trait(ctx.db, default_trait, &[]))) + .unwrap_or(false); + + let missing_fields = ctx.sema.record_literal_missing_fields(record_lit); + if impl_default_trait && !missing_fields.is_empty() { + acc.add( + CompletionItem::new( + CompletionKind::Snippet, + ctx.source_range(), + "..Default::default()", + ) + .insert_text("..Default::default()") + .kind(CompletionItemKind::Field) + .build(), + ); + } + + missing_fields + } }; for (field, ty) in missing_fields { - acc.add_field(ctx, field, &ty) + acc.add_field(ctx, field, &ty); } Some(()) @@ -18,6 +45,7 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> #[cfg(test)] mod tests { + use assists::utils::FamousDefs; use expect_test::{expect, Expect}; use crate::{test_utils::completion_list, CompletionKind}; @@ -27,6 +55,80 @@ mod tests { expect.assert_eq(&actual); } + fn check_snippet(ra_fixture: &str, expect: Expect) { + let actual = completion_list( + &format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE), + CompletionKind::Snippet, + ); + expect.assert_eq(&actual); + } + + #[test] + fn test_record_literal_field_default() { + let test_code = r#" +struct S { foo: u32, bar: usize } + +impl core::default::Default for S { + fn default() -> Self { + S { + foo: 0, + bar: 0, + } + } +} + +fn process(f: S) { + let other = S { + foo: 5, + .<|> + }; +} +"#; + check( + test_code, + expect![[r#" + fd bar usize + "#]], + ); + + check_snippet( + test_code, + expect![[r#" + fd ..Default::default() + sn pd + sn ppd + "#]], + ); + } + + #[test] + fn test_record_literal_field_without_default() { + let test_code = r#" +struct S { foo: u32, bar: usize } + +fn process(f: S) { + let other = S { + foo: 5, + .<|> + }; +} +"#; + check( + test_code, + expect![[r#" + fd bar usize + "#]], + ); + + check_snippet( + test_code, + expect![[r#" + sn pd + sn ppd + "#]], + ); + } + #[test] fn test_record_pattern_field() { check(