From f4ce0d78bb03b89c1dc96a63c35ea751b9708fb9 Mon Sep 17 00:00:00 2001 From: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> Date: Thu, 6 Jan 2022 15:42:29 +0100 Subject: [PATCH] add better default behavior on fill struct fields diagnostic Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> --- crates/hir/src/lib.rs | 5 +- crates/hir_ty/src/method_resolution.rs | 1 + .../src/handlers/missing_fields.rs | 119 ++++++++++++++---- crates/ide_diagnostics/src/tests.rs | 16 ++- 4 files changed, 103 insertions(+), 38 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 8d5dbd28ac8..3b734ac6629 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2695,7 +2695,7 @@ pub fn autoderef_<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator( - self, + &self, db: &dyn HirDatabase, krate: Crate, mut callback: impl FnMut(AssocItem) -> Option, @@ -2709,7 +2709,7 @@ pub fn iterate_assoc_items( } fn iterate_assoc_items_dyn( - self, + &self, db: &dyn HirDatabase, krate: Crate, callback: &mut dyn FnMut(AssocItemId) -> bool, @@ -2751,6 +2751,7 @@ pub fn iterate_method_candidates( ) -> Option { let _p = profile::span("iterate_method_candidates"); let mut slot = None; + self.iterate_method_candidates_dyn( db, krate, diff --git a/crates/hir_ty/src/method_resolution.rs b/crates/hir_ty/src/method_resolution.rs index cb6b6ec39fb..1a451ae79f5 100644 --- a/crates/hir_ty/src/method_resolution.rs +++ b/crates/hir_ty/src/method_resolution.rs @@ -542,6 +542,7 @@ pub fn iterate_method_candidates_dyn( let deref_chain = autoderef_method_receiver(db, krate, ty); let mut deref_chains = stdx::slice_tails(&deref_chain); + deref_chains.try_for_each(|deref_chain| { iterate_method_candidates_with_autoref( deref_chain, diff --git a/crates/ide_diagnostics/src/handlers/missing_fields.rs b/crates/ide_diagnostics/src/handlers/missing_fields.rs index cded84c54eb..8aca6ab3752 100644 --- a/crates/ide_diagnostics/src/handlers/missing_fields.rs +++ b/crates/ide_diagnostics/src/handlers/missing_fields.rs @@ -1,7 +1,7 @@ use either::Either; use hir::{ db::{AstDatabase, HirDatabase}, - known, HirDisplay, InFile, SemanticsScope, Type, + known, AssocItem, HirDisplay, InFile, Type, }; use ide_db::{assists::Assist, helpers::FamousDefs, source_change::SourceChange}; use rustc_hash::FxHashMap; @@ -74,8 +74,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option Some(make::ext::expr_todo()), crate::ExprFillDefaultMode::DefaultImpl => { - let scope = ctx.sema.scope(&root); - let default_constr = get_default_constructor(ctx, d, &scope, ty); + let default_constr = get_default_constructor(ctx, d, ty); match default_constr { Some(default_constr) => Some(default_constr), _ => 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( ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields, - scope: &SemanticsScope, ty: &Type, ) -> Option { if let Some(builtin_ty) = ty.as_builtin() { @@ -151,33 +149,35 @@ fn get_default_constructor( return Some(make::ext::empty_str()); } } + let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate(); 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 - // FIXME: doesn't work for now - let has_new_method = ty - .iterate_method_candidates( - ctx.sema.db, - krate, - &traits_in_scope, - Some(&known::new), - |_, func| { - if func.assoc_fn_params(ctx.sema.db).is_empty() + // Look for a ::new() associated function + let has_new_func = ty + .iterate_assoc_items(ctx.sema.db, krate, |assoc_item| { + if let AssocItem::Function(func) = assoc_item { + if func.name(ctx.sema.db) == known::new + && func.assoc_fn_params(ctx.sema.db).is_empty() && func.self_param(ctx.sema.db).is_none() { return Some(()); } - None - }, - ) + } + + None + }) .is_some(); - if has_new_method { + if has_new_func { 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))) } else { None @@ -264,7 +264,7 @@ fn here() {} macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } 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 } @@ -286,7 +286,7 @@ fn test_fn() { struct TestStruct { one: i32, two: i64 } 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 } 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 } 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 } fn test_fn() { - TestStruct { r#type: todo!() }; + TestStruct { r#type: 0 }; } ", ); @@ -485,7 +550,7 @@ fn f() { let b = 1usize; S { a, - b: todo!(), + b: 0, }; } "#, diff --git a/crates/ide_diagnostics/src/tests.rs b/crates/ide_diagnostics/src/tests.rs index a2b92c4ff91..479a121c681 100644 --- a/crates/ide_diagnostics/src/tests.rs +++ b/crates/ide_diagnostics/src/tests.rs @@ -9,7 +9,7 @@ use stdx::trim_indent; 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, /// 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 (db, file_position) = RootDatabase::with_position(ra_fixture_before); - let diagnostic = super::diagnostics( - &db, - &DiagnosticsConfig::default(), - &AssistResolveStrategy::All, - file_position.file_id, - ) - .pop() - .expect("no diagnostics"); + let mut conf = DiagnosticsConfig::default(); + conf.expr_fill_default = ExprFillDefaultMode::DefaultImpl; + let diagnostic = + super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id) + .pop() + .expect("no diagnostics"); let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth]; let actual = { let source_change = fix.source_change.as_ref().unwrap(); -- 2.44.0