Skip to content

Commit 40009e0

Browse files
bors[bot]bnjjj
andauthored
Merge #11145
11145: feat: add config to use reasonable default expression instead of todo! when filling missing fields r=Veykril a=bnjjj Use `Default::default()` in struct fields when we ask to fill it instead of putting `todo!()` for every fields before: ```rust pub enum Other { One, Two, } pub struct Test { text: String, num: usize, other: Other, } fn t_test() { let test = Test {<|>}; } ``` after: ```rust pub enum Other { One, Two, } pub struct Test { text: String, num: usize, other: Other, } fn t_test() { let test = Test { text: String::new(), num: 0, other: todo!(), }; } ``` Co-authored-by: Benjamin Coenen <[email protected]> Co-authored-by: Coenen Benjamin <[email protected]>
2 parents efb9b89 + 8e0a05e commit 40009e0

File tree

11 files changed

+271
-25
lines changed

11 files changed

+271
-25
lines changed

crates/hir/src/lib.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,6 +1689,26 @@ impl BuiltinType {
16891689
pub fn name(self) -> Name {
16901690
self.inner.as_name()
16911691
}
1692+
1693+
pub fn is_int(&self) -> bool {
1694+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Int(_))
1695+
}
1696+
1697+
pub fn is_uint(&self) -> bool {
1698+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Uint(_))
1699+
}
1700+
1701+
pub fn is_float(&self) -> bool {
1702+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Float(_))
1703+
}
1704+
1705+
pub fn is_char(&self) -> bool {
1706+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Char)
1707+
}
1708+
1709+
pub fn is_str(&self) -> bool {
1710+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Str)
1711+
}
16921712
}
16931713

16941714
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -2615,6 +2635,10 @@ impl Type {
26152635
matches!(&self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
26162636
}
26172637

2638+
pub fn is_array(&self) -> bool {
2639+
matches!(&self.ty.kind(Interner), TyKind::Array(..))
2640+
}
2641+
26182642
pub fn is_packed(&self, db: &dyn HirDatabase) -> bool {
26192643
let adt_id = match *self.ty.kind(Interner) {
26202644
TyKind::Adt(hir_ty::AdtId(adt_id), ..) => adt_id,
@@ -2713,7 +2737,7 @@ impl Type {
27132737
// This would be nicer if it just returned an iterator, but that runs into
27142738
// lifetime problems, because we need to borrow temp `CrateImplDefs`.
27152739
pub fn iterate_assoc_items<T>(
2716-
self,
2740+
&self,
27172741
db: &dyn HirDatabase,
27182742
krate: Crate,
27192743
mut callback: impl FnMut(AssocItem) -> Option<T>,
@@ -2727,7 +2751,7 @@ impl Type {
27272751
}
27282752

27292753
fn iterate_assoc_items_dyn(
2730-
self,
2754+
&self,
27312755
db: &dyn HirDatabase,
27322756
krate: Crate,
27332757
callback: &mut dyn FnMut(AssocItemId) -> bool,
@@ -2769,6 +2793,7 @@ impl Type {
27692793
) -> Option<T> {
27702794
let _p = profile::span("iterate_method_candidates");
27712795
let mut slot = None;
2796+
27722797
self.iterate_method_candidates_dyn(
27732798
db,
27742799
krate,

crates/hir_expand/src/name.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ pub mod known {
226226
iter_mut,
227227
len,
228228
is_empty,
229+
new,
229230
// Builtin macros
230231
asm,
231232
assert,

crates/hir_ty/src/method_resolution.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ pub fn iterate_method_candidates_dyn(
542542

543543
let deref_chain = autoderef_method_receiver(db, krate, ty);
544544
let mut deref_chains = stdx::slice_tails(&deref_chain);
545+
545546
deref_chains.try_for_each(|deref_chain| {
546547
iterate_method_candidates_with_autoref(
547548
deref_chain,

crates/ide/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ pub use ide_db::{
118118
symbol_index::Query,
119119
RootDatabase, SymbolKind,
120120
};
121-
pub use ide_diagnostics::{Diagnostic, DiagnosticsConfig, Severity};
121+
pub use ide_diagnostics::{Diagnostic, DiagnosticsConfig, ExprFillDefaultMode, Severity};
122122
pub use ide_ssr::SsrError;
123123
pub use syntax::{TextRange, TextSize};
124124
pub use text_edit::{Indel, TextEdit};

crates/ide_diagnostics/src/handlers/missing_fields.rs

Lines changed: 157 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
use either::Either;
2-
use hir::{db::AstDatabase, InFile};
3-
use ide_db::{assists::Assist, source_change::SourceChange};
2+
use hir::{
3+
db::{AstDatabase, HirDatabase},
4+
known, AssocItem, HirDisplay, InFile, Type,
5+
};
6+
use ide_db::{assists::Assist, helpers::FamousDefs, source_change::SourceChange};
47
use rustc_hash::FxHashMap;
58
use stdx::format_to;
6-
use syntax::{algo, ast::make, AstNode, SyntaxNodePtr};
9+
use syntax::{
10+
algo,
11+
ast::{self, make},
12+
AstNode, SyntaxNodePtr,
13+
};
714
use text_edit::TextEdit;
815

916
use crate::{fix, Diagnostic, DiagnosticsContext};
@@ -63,17 +70,29 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
6370
}
6471
});
6572
let missing_fields = ctx.sema.record_literal_missing_fields(&field_list_parent);
73+
74+
let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default {
75+
crate::ExprFillDefaultMode::Todo => Some(make::ext::expr_todo()),
76+
crate::ExprFillDefaultMode::Default => {
77+
let default_constr = get_default_constructor(ctx, d, ty);
78+
match default_constr {
79+
Some(default_constr) => Some(default_constr),
80+
_ => Some(make::ext::expr_todo()),
81+
}
82+
}
83+
};
84+
6685
for (f, ty) in missing_fields.iter() {
6786
let field_expr = if let Some(local_candidate) = locals.get(&f.name(ctx.sema.db)) {
6887
cov_mark::hit!(field_shorthand);
6988
let candidate_ty = local_candidate.ty(ctx.sema.db);
7089
if ty.could_unify_with(ctx.sema.db, &candidate_ty) {
7190
None
7291
} else {
73-
Some(make::ext::expr_todo())
92+
generate_fill_expr(ty)
7493
}
7594
} else {
76-
Some(make::ext::expr_todo())
95+
generate_fill_expr(ty)
7796
};
7897
let field =
7998
make::record_expr_field(make::name_ref(&f.name(ctx.sema.db).to_smol_str()), field_expr)
@@ -102,6 +121,68 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
102121
)])
103122
}
104123

124+
fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
125+
let ty_str = match ty.as_adt() {
126+
Some(adt) => adt.name(db).to_string(),
127+
None => ty.display_source_code(db, module.into()).ok().unwrap_or_else(|| "_".to_string()),
128+
};
129+
130+
make::ty(&ty_str)
131+
}
132+
133+
fn get_default_constructor(
134+
ctx: &DiagnosticsContext<'_>,
135+
d: &hir::MissingFields,
136+
ty: &Type,
137+
) -> Option<ast::Expr> {
138+
if let Some(builtin_ty) = ty.as_builtin() {
139+
if builtin_ty.is_int() || builtin_ty.is_uint() {
140+
return Some(make::ext::zero_number());
141+
}
142+
if builtin_ty.is_float() {
143+
return Some(make::ext::zero_float());
144+
}
145+
if builtin_ty.is_char() {
146+
return Some(make::ext::empty_char());
147+
}
148+
if builtin_ty.is_str() {
149+
return Some(make::ext::empty_str());
150+
}
151+
}
152+
153+
let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
154+
let module = krate.root_module(ctx.sema.db);
155+
156+
// Look for a ::new() associated function
157+
let has_new_func = ty
158+
.iterate_assoc_items(ctx.sema.db, krate, |assoc_item| {
159+
if let AssocItem::Function(func) = assoc_item {
160+
if func.name(ctx.sema.db) == known::new
161+
&& func.assoc_fn_params(ctx.sema.db).is_empty()
162+
{
163+
return Some(());
164+
}
165+
}
166+
167+
None
168+
})
169+
.is_some();
170+
171+
if has_new_func {
172+
Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module)))
173+
} else if !ty.is_array()
174+
&& ty.impls_trait(
175+
ctx.sema.db,
176+
FamousDefs(&ctx.sema, Some(krate)).core_default_Default()?,
177+
&[],
178+
)
179+
{
180+
Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module)))
181+
} else {
182+
None
183+
}
184+
}
185+
105186
#[cfg(test)]
106187
mod tests {
107188
use crate::tests::{check_diagnostics, check_fix};
@@ -182,7 +263,7 @@ fn here() {}
182263
macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
183264
184265
fn main() {
185-
let _x = id![Foo {a:42, b: todo!() }];
266+
let _x = id![Foo {a:42, b: 0 }];
186267
}
187268
188269
pub struct Foo { pub a: i32, pub b: i32 }
@@ -204,7 +285,7 @@ fn test_fn() {
204285
struct TestStruct { one: i32, two: i64 }
205286
206287
fn test_fn() {
207-
let s = TestStruct { one: todo!(), two: todo!() };
288+
let s = TestStruct { one: 0, two: 0 };
208289
}
209290
"#,
210291
);
@@ -224,7 +305,7 @@ impl TestStruct {
224305
struct TestStruct { one: i32 }
225306
226307
impl TestStruct {
227-
fn test_fn() { let s = Self { one: todo!() }; }
308+
fn test_fn() { let s = Self { one: 0 }; }
228309
}
229310
"#,
230311
);
@@ -272,7 +353,72 @@ fn test_fn() {
272353
struct TestStruct { one: i32, two: i64 }
273354
274355
fn test_fn() {
275-
let s = TestStruct{ two: 2, one: todo!() };
356+
let s = TestStruct{ two: 2, one: 0 };
357+
}
358+
",
359+
);
360+
}
361+
362+
#[test]
363+
fn test_fill_struct_fields_new() {
364+
check_fix(
365+
r#"
366+
struct TestWithNew(usize);
367+
impl TestWithNew {
368+
pub fn new() -> Self {
369+
Self(0)
370+
}
371+
}
372+
struct TestStruct { one: i32, two: TestWithNew }
373+
374+
fn test_fn() {
375+
let s = TestStruct{ $0 };
376+
}
377+
"#,
378+
r"
379+
struct TestWithNew(usize);
380+
impl TestWithNew {
381+
pub fn new() -> Self {
382+
Self(0)
383+
}
384+
}
385+
struct TestStruct { one: i32, two: TestWithNew }
386+
387+
fn test_fn() {
388+
let s = TestStruct{ one: 0, two: TestWithNew::new() };
389+
}
390+
",
391+
);
392+
}
393+
394+
#[test]
395+
fn test_fill_struct_fields_default() {
396+
check_fix(
397+
r#"
398+
//- minicore: default
399+
struct TestWithDefault(usize);
400+
impl Default for TestWithDefault {
401+
pub fn default() -> Self {
402+
Self(0)
403+
}
404+
}
405+
struct TestStruct { one: i32, two: TestWithDefault }
406+
407+
fn test_fn() {
408+
let s = TestStruct{ $0 };
409+
}
410+
"#,
411+
r"
412+
struct TestWithDefault(usize);
413+
impl Default for TestWithDefault {
414+
pub fn default() -> Self {
415+
Self(0)
416+
}
417+
}
418+
struct TestStruct { one: i32, two: TestWithDefault }
419+
420+
fn test_fn() {
421+
let s = TestStruct{ one: 0, two: TestWithDefault::default() };
276422
}
277423
",
278424
);
@@ -292,7 +438,7 @@ fn test_fn() {
292438
struct TestStruct { r#type: u8 }
293439
294440
fn test_fn() {
295-
TestStruct { r#type: todo!() };
441+
TestStruct { r#type: 0 };
296442
}
297443
",
298444
);
@@ -403,7 +549,7 @@ fn f() {
403549
let b = 1usize;
404550
S {
405551
a,
406-
b: todo!(),
552+
b: 0,
407553
};
408554
}
409555
"#,

crates/ide_diagnostics/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,22 @@ pub enum Severity {
129129
WeakWarning,
130130
}
131131

132+
#[derive(Clone, Debug, PartialEq, Eq)]
133+
pub enum ExprFillDefaultMode {
134+
Todo,
135+
Default,
136+
}
137+
impl Default for ExprFillDefaultMode {
138+
fn default() -> Self {
139+
Self::Todo
140+
}
141+
}
142+
132143
#[derive(Default, Debug, Clone)]
133144
pub struct DiagnosticsConfig {
134145
pub disable_experimental: bool,
135146
pub disabled: FxHashSet<String>,
147+
pub expr_fill_default: ExprFillDefaultMode,
136148
}
137149

138150
struct DiagnosticsContext<'a> {

crates/ide_diagnostics/src/tests.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use ide_db::{
99
use stdx::trim_indent;
1010
use test_utils::{assert_eq_text, extract_annotations};
1111

12-
use crate::{DiagnosticsConfig, Severity};
12+
use crate::{DiagnosticsConfig, ExprFillDefaultMode, Severity};
1313

1414
/// Takes a multi-file input fixture with annotated cursor positions,
1515
/// and checks that:
@@ -36,14 +36,12 @@ fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
3636
let after = trim_indent(ra_fixture_after);
3737

3838
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
39-
let diagnostic = super::diagnostics(
40-
&db,
41-
&DiagnosticsConfig::default(),
42-
&AssistResolveStrategy::All,
43-
file_position.file_id,
44-
)
45-
.pop()
46-
.expect("no diagnostics");
39+
let mut conf = DiagnosticsConfig::default();
40+
conf.expr_fill_default = ExprFillDefaultMode::Default;
41+
let diagnostic =
42+
super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id)
43+
.pop()
44+
.expect("no diagnostics");
4745
let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth];
4846
let actual = {
4947
let source_change = fix.source_change.as_ref().unwrap();

0 commit comments

Comments
 (0)