mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 04:44:57 +00:00
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:
parent
336c899a07
commit
f4ce0d78bb
4 changed files with 103 additions and 38 deletions
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue