Skip to content

Commit 336c899

Browse files
committed
add better default behavior on fill struct fields diagnostic
Signed-off-by: Benjamin Coenen <[email protected]>
1 parent df6fa50 commit 336c899

File tree

6 files changed

+123
-13
lines changed

6 files changed

+123
-13
lines changed

crates/hir/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,6 +1685,26 @@ impl BuiltinType {
16851685
pub fn name(self) -> Name {
16861686
self.inner.as_name()
16871687
}
1688+
1689+
pub fn is_int(&self) -> bool {
1690+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Int(_))
1691+
}
1692+
1693+
pub fn is_uint(&self) -> bool {
1694+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Uint(_))
1695+
}
1696+
1697+
pub fn is_float(&self) -> bool {
1698+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Float(_))
1699+
}
1700+
1701+
pub fn is_char(&self) -> bool {
1702+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Char)
1703+
}
1704+
1705+
pub fn is_str(&self) -> bool {
1706+
matches!(self.inner, hir_def::builtin_type::BuiltinType::Str)
1707+
}
16881708
}
16891709

16901710
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -2573,6 +2593,10 @@ impl Type {
25732593
matches!(&self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
25742594
}
25752595

2596+
pub fn is_array(&self) -> bool {
2597+
matches!(&self.ty.kind(Interner), TyKind::Array(..))
2598+
}
2599+
25762600
pub fn is_packed(&self, db: &dyn HirDatabase) -> bool {
25772601
let adt_id = match *self.ty.kind(Interner) {
25782602
TyKind::Adt(hir_ty::AdtId(adt_id), ..) => adt_id,

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/ide_diagnostics/src/handlers/missing_fields.rs

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
use either::Either;
2-
use hir::{db::AstDatabase, InFile, Type};
2+
use hir::{
3+
db::{AstDatabase, HirDatabase},
4+
known, HirDisplay, InFile, SemanticsScope, Type,
5+
};
36
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};
@@ -67,13 +74,10 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
6774
let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default {
6875
crate::ExprFillDefaultMode::Todo => Some(make::ext::expr_todo()),
6976
crate::ExprFillDefaultMode::DefaultImpl => {
70-
let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
71-
let default_trait = FamousDefs(&ctx.sema, Some(krate)).core_default_Default();
72-
73-
match default_trait {
74-
Some(default_trait) if ty.impls_trait(ctx.sema.db, default_trait, &[]) => {
75-
Some(make::ext::expr_default())
76-
}
77+
let scope = ctx.sema.scope(&root);
78+
let default_constr = get_default_constructor(ctx, d, &scope, ty);
79+
match default_constr {
80+
Some(default_constr) => Some(default_constr),
7781
_ => Some(make::ext::expr_todo()),
7882
}
7983
}
@@ -118,6 +122,68 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
118122
)])
119123
}
120124

125+
fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
126+
let ty_str = match ty.as_adt() {
127+
Some(adt) => adt.name(db).to_string(),
128+
None => ty.display_source_code(db, module.into()).ok().unwrap_or_else(|| "_".to_string()),
129+
};
130+
131+
make::ty(&ty_str)
132+
}
133+
134+
fn get_default_constructor(
135+
ctx: &DiagnosticsContext<'_>,
136+
d: &hir::MissingFields,
137+
scope: &SemanticsScope,
138+
ty: &Type,
139+
) -> Option<ast::Expr> {
140+
if let Some(builtin_ty) = ty.as_builtin() {
141+
if builtin_ty.is_int() || builtin_ty.is_uint() {
142+
return Some(make::ext::zero_number());
143+
}
144+
if builtin_ty.is_float() {
145+
return Some(make::ext::zero_float());
146+
}
147+
if builtin_ty.is_char() {
148+
return Some(make::ext::empty_char());
149+
}
150+
if builtin_ty.is_str() {
151+
return Some(make::ext::empty_str());
152+
}
153+
}
154+
let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
155+
let module = krate.root_module(ctx.sema.db);
156+
let default_trait = FamousDefs(&ctx.sema, Some(krate)).core_default_Default()?;
157+
let traits_in_scope = scope.visible_traits();
158+
159+
// Look for a ::new() method
160+
// FIXME: doesn't work for now
161+
let has_new_method = ty
162+
.iterate_method_candidates(
163+
ctx.sema.db,
164+
krate,
165+
&traits_in_scope,
166+
Some(&known::new),
167+
|_, func| {
168+
if func.assoc_fn_params(ctx.sema.db).is_empty()
169+
&& func.self_param(ctx.sema.db).is_none()
170+
{
171+
return Some(());
172+
}
173+
None
174+
},
175+
)
176+
.is_some();
177+
178+
if has_new_method {
179+
Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module)))
180+
} else if !ty.is_array() && ty.impls_trait(ctx.sema.db, default_trait, &[]) {
181+
Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module)))
182+
} else {
183+
None
184+
}
185+
}
186+
121187
#[cfg(test)]
122188
mod tests {
123189
use crate::tests::{check_diagnostics, check_fix};

crates/rust-analyzer/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
12731273
"enum": ["todo", "defaultImpl"],
12741274
"enumDescriptions": [
12751275
"Fill missing elements with 'todo' macro",
1276-
"Fill missing elements with Default::default()"
1276+
"Fill missing elements with T::default()"
12771277
],
12781278
},
12791279
"ImportGranularityDef" => set! {

crates/syntax/src/ast/make.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,27 @@ pub mod ext {
5959
pub fn expr_todo() -> ast::Expr {
6060
expr_from_text("todo!()")
6161
}
62-
pub fn expr_default() -> ast::Expr {
63-
expr_from_text("Default::default()")
62+
pub fn expr_ty_default(ty: &ast::Type) -> ast::Expr {
63+
expr_from_text(&format!("{}::default()", ty))
64+
}
65+
pub fn expr_ty_new(ty: &ast::Type) -> ast::Expr {
66+
expr_from_text(&format!("{}::new()", ty))
67+
}
68+
69+
pub fn zero_number() -> ast::Expr {
70+
expr_from_text("0")
71+
}
72+
pub fn zero_float() -> ast::Expr {
73+
expr_from_text("0.0")
74+
}
75+
pub fn empty_str() -> ast::Expr {
76+
expr_from_text(r#""""#)
77+
}
78+
pub fn empty_char() -> ast::Expr {
79+
expr_from_text("''")
80+
}
81+
pub fn default_bool() -> ast::Expr {
82+
expr_from_text("false")
6483
}
6584
pub fn empty_block_expr() -> ast::BlockExpr {
6685
block_expr(None, None)

editors/code/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@
388388
],
389389
"enumDescriptions": [
390390
"Fill missing elements with 'todo' macro",
391-
"Fill missing elements with Default::default()"
391+
"Fill missing elements with T::default()"
392392
]
393393
},
394394
"rust-analyzer.assist.importGranularity": {

0 commit comments

Comments
 (0)