add suggestion ..Default::default() for remaining struct fields in a constructor #6492

Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
This commit is contained in:
Benjamin Coenen 2020-11-13 17:17:16 +01:00
parent 9beec7c263
commit e73d140b51
4 changed files with 118 additions and 4 deletions

1
Cargo.lock generated
View file

@ -253,6 +253,7 @@ dependencies = [
name = "completion" name = "completion"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"assists",
"base_db", "base_db",
"expect-test", "expect-test",
"hir", "hir",

View file

@ -257,6 +257,12 @@ pub mod convert {
} }
} }
pub mod default {
pub trait Default: Sized {
fn default() -> Self;
}
}
pub mod iter { pub mod iter {
pub use self::traits::{collect::IntoIterator, iterator::Iterator}; pub use self::traits::{collect::IntoIterator, iterator::Iterator};
mod traits { mod traits {
@ -327,7 +333,7 @@ pub mod option {
} }
pub mod prelude { 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] #[prelude_import]
pub use prelude::*; pub use prelude::*;
@ -345,6 +351,10 @@ pub use prelude::*;
self.find_enum("core:option:Option") self.find_enum("core:option:Option")
} }
pub fn core_default_Default(&self) -> Option<Trait> {
self.find_trait("core:default:Default")
}
pub fn core_iter_Iterator(&self) -> Option<Trait> { pub fn core_iter_Iterator(&self) -> Option<Trait> {
self.find_trait("core:iter:traits:iterator:Iterator") self.find_trait("core:iter:traits:iterator:Iterator")
} }

View file

@ -14,6 +14,7 @@ itertools = "0.9.0"
log = "0.4.8" log = "0.4.8"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
assists = { path = "../assists", version = "0.0.0" }
stdx = { path = "../stdx", version = "0.0.0" } stdx = { path = "../stdx", version = "0.0.0" }
syntax = { path = "../syntax", version = "0.0.0" } syntax = { path = "../syntax", version = "0.0.0" }
text_edit = { path = "../text_edit", version = "0.0.0" } text_edit = { path = "../text_edit", version = "0.0.0" }

View file

@ -1,16 +1,43 @@
//! Complete fields in record literals and patterns. //! 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<()> { 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()) { let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) {
(None, None) => return None, (None, None) => return None,
(Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), (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_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 { for (field, ty) in missing_fields {
acc.add_field(ctx, field, &ty) acc.add_field(ctx, field, &ty);
} }
Some(()) Some(())
@ -18,6 +45,7 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use assists::utils::FamousDefs;
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};
use crate::{test_utils::completion_list, CompletionKind}; use crate::{test_utils::completion_list, CompletionKind};
@ -27,6 +55,80 @@ mod tests {
expect.assert_eq(&actual); 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] #[test]
fn test_record_pattern_field() { fn test_record_pattern_field() {
check( check(