about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBenjamin Coenen <5719034+bnjjj@users.noreply.github.com>2022-01-04 15:59:00 +0100
committerBenjamin Coenen <5719034+bnjjj@users.noreply.github.com>2022-01-04 15:59:00 +0100
commit336c899a07063c56c4a0b9f4764fbe5f9729ea51 (patch)
treee009ff7c5f790d8071ec0b0cad3a944ab2fe1ff5
parentdf6fa50f92af1232df5c044b8fe223817588ec8b (diff)
downloadrust-336c899a07063c56c4a0b9f4764fbe5f9729ea51.tar.gz
rust-336c899a07063c56c4a0b9f4764fbe5f9729ea51.zip
add better default behavior on fill struct fields diagnostic
Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
-rw-r--r--crates/hir/src/lib.rs24
-rw-r--r--crates/hir_expand/src/name.rs1
-rw-r--r--crates/ide_diagnostics/src/handlers/missing_fields.rs84
-rw-r--r--crates/rust-analyzer/src/config.rs2
-rw-r--r--crates/syntax/src/ast/make.rs23
-rw-r--r--editors/code/package.json2
6 files changed, 123 insertions, 13 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 6306ae534da..8d5dbd28ac8 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1685,6 +1685,26 @@ impl BuiltinType {
     pub fn name(self) -> Name {
         self.inner.as_name()
     }
+
+    pub fn is_int(&self) -> bool {
+        matches!(self.inner, hir_def::builtin_type::BuiltinType::Int(_))
+    }
+
+    pub fn is_uint(&self) -> bool {
+        matches!(self.inner, hir_def::builtin_type::BuiltinType::Uint(_))
+    }
+
+    pub fn is_float(&self) -> bool {
+        matches!(self.inner, hir_def::builtin_type::BuiltinType::Float(_))
+    }
+
+    pub fn is_char(&self) -> bool {
+        matches!(self.inner, hir_def::builtin_type::BuiltinType::Char)
+    }
+
+    pub fn is_str(&self) -> bool {
+        matches!(self.inner, hir_def::builtin_type::BuiltinType::Str)
+    }
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -2573,6 +2593,10 @@ impl Type {
         matches!(&self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
     }
 
+    pub fn is_array(&self) -> bool {
+        matches!(&self.ty.kind(Interner), TyKind::Array(..))
+    }
+
     pub fn is_packed(&self, db: &dyn HirDatabase) -> bool {
         let adt_id = match *self.ty.kind(Interner) {
             TyKind::Adt(hir_ty::AdtId(adt_id), ..) => adt_id,
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index 23f81d3c7f5..3636ab62131 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -226,6 +226,7 @@ pub mod known {
         iter_mut,
         len,
         is_empty,
+        new,
         // Builtin macros
         asm,
         assert,
diff --git a/crates/ide_diagnostics/src/handlers/missing_fields.rs b/crates/ide_diagnostics/src/handlers/missing_fields.rs
index b5860fb8587..cded84c54eb 100644
--- a/crates/ide_diagnostics/src/handlers/missing_fields.rs
+++ b/crates/ide_diagnostics/src/handlers/missing_fields.rs
@@ -1,9 +1,16 @@
 use either::Either;
-use hir::{db::AstDatabase, InFile, Type};
+use hir::{
+    db::{AstDatabase, HirDatabase},
+    known, HirDisplay, InFile, SemanticsScope, Type,
+};
 use ide_db::{assists::Assist, helpers::FamousDefs, source_change::SourceChange};
 use rustc_hash::FxHashMap;
 use stdx::format_to;
-use syntax::{algo, ast::make, AstNode, SyntaxNodePtr};
+use syntax::{
+    algo,
+    ast::{self, make},
+    AstNode, SyntaxNodePtr,
+};
 use text_edit::TextEdit;
 
 use crate::{fix, Diagnostic, DiagnosticsContext};
@@ -67,13 +74,10 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
     let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default {
         crate::ExprFillDefaultMode::Todo => Some(make::ext::expr_todo()),
         crate::ExprFillDefaultMode::DefaultImpl => {
-            let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
-            let default_trait = FamousDefs(&ctx.sema, Some(krate)).core_default_Default();
-
-            match default_trait {
-                Some(default_trait) if ty.impls_trait(ctx.sema.db, default_trait, &[]) => {
-                    Some(make::ext::expr_default())
-                }
+            let scope = ctx.sema.scope(&root);
+            let default_constr = get_default_constructor(ctx, d, &scope, ty);
+            match default_constr {
+                Some(default_constr) => Some(default_constr),
                 _ => Some(make::ext::expr_todo()),
             }
         }
@@ -118,6 +122,68 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
     )])
 }
 
+fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
+    let ty_str = match ty.as_adt() {
+        Some(adt) => adt.name(db).to_string(),
+        None => ty.display_source_code(db, module.into()).ok().unwrap_or_else(|| "_".to_string()),
+    };
+
+    make::ty(&ty_str)
+}
+
+fn get_default_constructor(
+    ctx: &DiagnosticsContext<'_>,
+    d: &hir::MissingFields,
+    scope: &SemanticsScope,
+    ty: &Type,
+) -> Option<ast::Expr> {
+    if let Some(builtin_ty) = ty.as_builtin() {
+        if builtin_ty.is_int() || builtin_ty.is_uint() {
+            return Some(make::ext::zero_number());
+        }
+        if builtin_ty.is_float() {
+            return Some(make::ext::zero_float());
+        }
+        if builtin_ty.is_char() {
+            return Some(make::ext::empty_char());
+        }
+        if builtin_ty.is_str() {
+            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()
+                    && func.self_param(ctx.sema.db).is_none()
+                {
+                    return Some(());
+                }
+                None
+            },
+        )
+        .is_some();
+
+    if has_new_method {
+        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, &[]) {
+        Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module)))
+    } else {
+        None
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::tests::{check_diagnostics, check_fix};
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 9cef5584576..2d4d24e98d6 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -1273,7 +1273,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
             "enum": ["todo", "defaultImpl"],
             "enumDescriptions": [
                 "Fill missing elements with 'todo' macro",
-                "Fill missing elements with Default::default()"
+                "Fill missing elements with T::default()"
             ],
         },
         "ImportGranularityDef" => set! {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 1a46b84c966..12d5b7decc6 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -59,8 +59,27 @@ pub mod ext {
     pub fn expr_todo() -> ast::Expr {
         expr_from_text("todo!()")
     }
-    pub fn expr_default() -> ast::Expr {
-        expr_from_text("Default::default()")
+    pub fn expr_ty_default(ty: &ast::Type) -> ast::Expr {
+        expr_from_text(&format!("{}::default()", ty))
+    }
+    pub fn expr_ty_new(ty: &ast::Type) -> ast::Expr {
+        expr_from_text(&format!("{}::new()", ty))
+    }
+
+    pub fn zero_number() -> ast::Expr {
+        expr_from_text("0")
+    }
+    pub fn zero_float() -> ast::Expr {
+        expr_from_text("0.0")
+    }
+    pub fn empty_str() -> ast::Expr {
+        expr_from_text(r#""""#)
+    }
+    pub fn empty_char() -> ast::Expr {
+        expr_from_text("''")
+    }
+    pub fn default_bool() -> ast::Expr {
+        expr_from_text("false")
     }
     pub fn empty_block_expr() -> ast::BlockExpr {
         block_expr(None, None)
diff --git a/editors/code/package.json b/editors/code/package.json
index cbe9bf54427..6bc62fc9d7f 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -388,7 +388,7 @@
                     ],
                     "enumDescriptions": [
                         "Fill missing elements with 'todo' macro",
-                        "Fill missing elements with Default::default()"
+                        "Fill missing elements with T::default()"
                     ]
                 },
                 "rust-analyzer.assist.importGranularity": {