add better default behavior on fill struct fields diagnostic

Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
This commit is contained in:
Benjamin Coenen 2022-01-06 15:42:29 +01:00
parent 336c899a07
commit f4ce0d78bb
4 changed files with 103 additions and 38 deletions

View file

@ -2695,7 +2695,7 @@ impl Type {
// This would be nicer if it just returned an iterator, but that runs into // This would be nicer if it just returned an iterator, but that runs into
// lifetime problems, because we need to borrow temp `CrateImplDefs`. // lifetime problems, because we need to borrow temp `CrateImplDefs`.
pub fn iterate_assoc_items<T>( pub fn iterate_assoc_items<T>(
self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
krate: Crate, krate: Crate,
mut callback: impl FnMut(AssocItem) -> Option<T>, mut callback: impl FnMut(AssocItem) -> Option<T>,
@ -2709,7 +2709,7 @@ impl Type {
} }
fn iterate_assoc_items_dyn( fn iterate_assoc_items_dyn(
self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
krate: Crate, krate: Crate,
callback: &mut dyn FnMut(AssocItemId) -> bool, callback: &mut dyn FnMut(AssocItemId) -> bool,
@ -2751,6 +2751,7 @@ impl Type {
) -> Option<T> { ) -> Option<T> {
let _p = profile::span("iterate_method_candidates"); let _p = profile::span("iterate_method_candidates");
let mut slot = None; let mut slot = None;
self.iterate_method_candidates_dyn( self.iterate_method_candidates_dyn(
db, db,
krate, krate,

View file

@ -542,6 +542,7 @@ pub fn iterate_method_candidates_dyn(
let deref_chain = autoderef_method_receiver(db, krate, ty); let deref_chain = autoderef_method_receiver(db, krate, ty);
let mut deref_chains = stdx::slice_tails(&deref_chain); let mut deref_chains = stdx::slice_tails(&deref_chain);
deref_chains.try_for_each(|deref_chain| { deref_chains.try_for_each(|deref_chain| {
iterate_method_candidates_with_autoref( iterate_method_candidates_with_autoref(
deref_chain, deref_chain,

View file

@ -1,7 +1,7 @@
use either::Either; use either::Either;
use hir::{ use hir::{
db::{AstDatabase, HirDatabase}, db::{AstDatabase, HirDatabase},
known, HirDisplay, InFile, SemanticsScope, Type, known, AssocItem, HirDisplay, InFile, Type,
}; };
use ide_db::{assists::Assist, helpers::FamousDefs, source_change::SourceChange}; use ide_db::{assists::Assist, helpers::FamousDefs, source_change::SourceChange};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -74,8 +74,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default { let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default {
crate::ExprFillDefaultMode::Todo => Some(make::ext::expr_todo()), crate::ExprFillDefaultMode::Todo => Some(make::ext::expr_todo()),
crate::ExprFillDefaultMode::DefaultImpl => { crate::ExprFillDefaultMode::DefaultImpl => {
let scope = ctx.sema.scope(&root); let default_constr = get_default_constructor(ctx, d, ty);
let default_constr = get_default_constructor(ctx, d, &scope, ty);
match default_constr { match default_constr {
Some(default_constr) => Some(default_constr), Some(default_constr) => Some(default_constr),
_ => Some(make::ext::expr_todo()), _ => Some(make::ext::expr_todo()),
@ -134,7 +133,6 @@ fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Ty
fn get_default_constructor( fn get_default_constructor(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::MissingFields, d: &hir::MissingFields,
scope: &SemanticsScope,
ty: &Type, ty: &Type,
) -> Option<ast::Expr> { ) -> Option<ast::Expr> {
if let Some(builtin_ty) = ty.as_builtin() { if let Some(builtin_ty) = ty.as_builtin() {
@ -151,33 +149,35 @@ fn get_default_constructor(
return Some(make::ext::empty_str()); return Some(make::ext::empty_str());
} }
} }
let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate(); let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
let module = krate.root_module(ctx.sema.db); let module = krate.root_module(ctx.sema.db);
let default_trait = FamousDefs(&ctx.sema, Some(krate)).core_default_Default()?;
let traits_in_scope = scope.visible_traits();
// Look for a ::new() method // Look for a ::new() associated function
// FIXME: doesn't work for now let has_new_func = ty
let has_new_method = ty .iterate_assoc_items(ctx.sema.db, krate, |assoc_item| {
.iterate_method_candidates( if let AssocItem::Function(func) = assoc_item {
ctx.sema.db, if func.name(ctx.sema.db) == known::new
krate, && func.assoc_fn_params(ctx.sema.db).is_empty()
&traits_in_scope,
Some(&known::new),
|_, func| {
if func.assoc_fn_params(ctx.sema.db).is_empty()
&& func.self_param(ctx.sema.db).is_none() && func.self_param(ctx.sema.db).is_none()
{ {
return Some(()); return Some(());
} }
None }
},
) None
})
.is_some(); .is_some();
if has_new_method { if has_new_func {
Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module))) Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module)))
} else if !ty.is_array() && ty.impls_trait(ctx.sema.db, default_trait, &[]) { } else if !ty.is_array()
&& ty.impls_trait(
ctx.sema.db,
FamousDefs(&ctx.sema, Some(krate)).core_default_Default()?,
&[],
)
{
Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module))) Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module)))
} else { } else {
None None
@ -264,7 +264,7 @@ fn here() {}
macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
fn main() { fn main() {
let _x = id![Foo {a:42, b: todo!() }]; let _x = id![Foo {a:42, b: 0 }];
} }
pub struct Foo { pub a: i32, pub b: i32 } pub struct Foo { pub a: i32, pub b: i32 }
@ -286,7 +286,7 @@ fn test_fn() {
struct TestStruct { one: i32, two: i64 } struct TestStruct { one: i32, two: i64 }
fn test_fn() { fn test_fn() {
let s = TestStruct { one: todo!(), two: todo!() }; let s = TestStruct { one: 0, two: 0 };
} }
"#, "#,
); );
@ -306,7 +306,7 @@ impl TestStruct {
struct TestStruct { one: i32 } struct TestStruct { one: i32 }
impl TestStruct { impl TestStruct {
fn test_fn() { let s = Self { one: todo!() }; } fn test_fn() { let s = Self { one: 0 }; }
} }
"#, "#,
); );
@ -354,7 +354,72 @@ fn test_fn() {
struct TestStruct { one: i32, two: i64 } struct TestStruct { one: i32, two: i64 }
fn test_fn() { fn test_fn() {
let s = TestStruct{ two: 2, one: todo!() }; let s = TestStruct{ two: 2, one: 0 };
}
",
);
}
#[test]
fn test_fill_struct_fields_new() {
check_fix(
r#"
struct TestWithNew(usize);
impl TestWithNew {
pub fn new() -> Self {
Self(0)
}
}
struct TestStruct { one: i32, two: TestWithNew }
fn test_fn() {
let s = TestStruct{ $0 };
}
"#,
r"
struct TestWithNew(usize);
impl TestWithNew {
pub fn new() -> Self {
Self(0)
}
}
struct TestStruct { one: i32, two: TestWithNew }
fn test_fn() {
let s = TestStruct{ one: 0, two: TestWithNew::new() };
}
",
);
}
#[test]
fn test_fill_struct_fields_default() {
check_fix(
r#"
//- minicore: default
struct TestWithDefault(usize);
impl Default for TestWithDefault {
pub fn default() -> Self {
Self(0)
}
}
struct TestStruct { one: i32, two: TestWithDefault }
fn test_fn() {
let s = TestStruct{ $0 };
}
"#,
r"
struct TestWithDefault(usize);
impl Default for TestWithDefault {
pub fn default() -> Self {
Self(0)
}
}
struct TestStruct { one: i32, two: TestWithDefault }
fn test_fn() {
let s = TestStruct{ one: 0, two: TestWithDefault::default() };
} }
", ",
); );
@ -374,7 +439,7 @@ fn test_fn() {
struct TestStruct { r#type: u8 } struct TestStruct { r#type: u8 }
fn test_fn() { fn test_fn() {
TestStruct { r#type: todo!() }; TestStruct { r#type: 0 };
} }
", ",
); );
@ -485,7 +550,7 @@ fn f() {
let b = 1usize; let b = 1usize;
S { S {
a, a,
b: todo!(), b: 0,
}; };
} }
"#, "#,

View file

@ -9,7 +9,7 @@ use ide_db::{
use stdx::trim_indent; use stdx::trim_indent;
use test_utils::{assert_eq_text, extract_annotations}; use test_utils::{assert_eq_text, extract_annotations};
use crate::{DiagnosticsConfig, Severity}; use crate::{DiagnosticsConfig, ExprFillDefaultMode, Severity};
/// Takes a multi-file input fixture with annotated cursor positions, /// Takes a multi-file input fixture with annotated cursor positions,
/// and checks that: /// and checks that:
@ -36,14 +36,12 @@ fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
let after = trim_indent(ra_fixture_after); let after = trim_indent(ra_fixture_after);
let (db, file_position) = RootDatabase::with_position(ra_fixture_before); let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
let diagnostic = super::diagnostics( let mut conf = DiagnosticsConfig::default();
&db, conf.expr_fill_default = ExprFillDefaultMode::DefaultImpl;
&DiagnosticsConfig::default(), let diagnostic =
&AssistResolveStrategy::All, super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id)
file_position.file_id, .pop()
) .expect("no diagnostics");
.pop()
.expect("no diagnostics");
let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth]; let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth];
let actual = { let actual = {
let source_change = fix.source_change.as_ref().unwrap(); let source_change = fix.source_change.as_ref().unwrap();