Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-assists/src/handlers/generate_new.rs139
-rw-r--r--crates/ide-db/src/lib.rs1
-rw-r--r--crates/ide-db/src/use_trivial_contructor.rs34
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_fields.rs117
4 files changed, 287 insertions, 4 deletions
diff --git a/crates/ide-assists/src/handlers/generate_new.rs b/crates/ide-assists/src/handlers/generate_new.rs
index 6a1f710f6d..5f4715bb53 100644
--- a/crates/ide-assists/src/handlers/generate_new.rs
+++ b/crates/ide-assists/src/handlers/generate_new.rs
@@ -1,3 +1,6 @@
+use ide_db::{
+ imports::import_assets::item_for_path_search, use_trivial_contructor::use_trivial_constructor,
+};
use itertools::Itertools;
use stdx::format_to;
use syntax::ast::{self, AstNode, HasName, HasVisibility, StructKind};
@@ -38,6 +41,8 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
// Return early if we've found an existing new fn
let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), "new")?;
+ let current_module = ctx.sema.scope(strukt.syntax())?.module();
+
let target = strukt.syntax().text_range();
acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
let mut buf = String::with_capacity(512);
@@ -48,11 +53,50 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
+ let trivial_constructors = field_list
+ .fields()
+ .map(|f| {
+ let ty = ctx.sema.resolve_type(&f.ty()?)?;
+
+ let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
+
+ let type_path = current_module
+ .find_use_path(ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?)?;
+
+ let expr = use_trivial_constructor(
+ &ctx.sema.db,
+ ide_db::helpers::mod_path_to_ast(&type_path),
+ &ty,
+ )?;
+
+ Some(format!("{}: {}", f.name()?.syntax(), expr))
+ })
+ .collect::<Vec<_>>();
+
let params = field_list
.fields()
- .filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
+ .enumerate()
+ .filter_map(|(i, f)| {
+ if trivial_constructors[i].is_none() {
+ Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax()))
+ } else {
+ None
+ }
+ })
+ .format(", ");
+
+ let fields = field_list
+ .fields()
+ .enumerate()
+ .filter_map(|(i, f)| {
+ let contructor = trivial_constructors[i].clone();
+ if contructor.is_some() {
+ contructor
+ } else {
+ Some(f.name()?.to_string())
+ }
+ })
.format(", ");
- let fields = field_list.fields().filter_map(|f| f.name()).format(", ");
format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
@@ -80,6 +124,97 @@ mod tests {
use super::*;
#[test]
+ fn test_generate_new_with_zst_fields() {
+ check_assist(
+ generate_new,
+ r#"
+struct Empty;
+
+struct Foo { empty: Empty $0}
+"#,
+ r#"
+struct Empty;
+
+struct Foo { empty: Empty }
+
+impl Foo {
+ fn $0new() -> Self { Self { empty: Empty } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+struct Empty;
+
+struct Foo { baz: String, empty: Empty $0}
+"#,
+ r#"
+struct Empty;
+
+struct Foo { baz: String, empty: Empty }
+
+impl Foo {
+ fn $0new(baz: String) -> Self { Self { baz, empty: Empty } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+enum Empty { Bar }
+
+struct Foo { empty: Empty $0}
+"#,
+ r#"
+enum Empty { Bar }
+
+struct Foo { empty: Empty }
+
+impl Foo {
+ fn $0new() -> Self { Self { empty: Empty::Bar } }
+}
+"#,
+ );
+
+ // make sure the assist only works on unit variants
+ check_assist(
+ generate_new,
+ r#"
+struct Empty {}
+
+struct Foo { empty: Empty $0}
+"#,
+ r#"
+struct Empty {}
+
+struct Foo { empty: Empty }
+
+impl Foo {
+ fn $0new(empty: Empty) -> Self { Self { empty } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+enum Empty { Bar {} }
+
+struct Foo { empty: Empty $0}
+"#,
+ r#"
+enum Empty { Bar {} }
+
+struct Foo { empty: Empty }
+
+impl Foo {
+ fn $0new(empty: Empty) -> Self { Self { empty } }
+}
+"#,
+ );
+ }
+
+ #[test]
fn test_generate_new() {
check_assist(
generate_new,
diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs
index 8fad4ac0f3..165b98d72e 100644
--- a/crates/ide-db/src/lib.rs
+++ b/crates/ide-db/src/lib.rs
@@ -20,6 +20,7 @@ pub mod source_change;
pub mod symbol_index;
pub mod traits;
pub mod ty_filter;
+pub mod use_trivial_contructor;
pub mod imports {
pub mod import_assets;
diff --git a/crates/ide-db/src/use_trivial_contructor.rs b/crates/ide-db/src/use_trivial_contructor.rs
new file mode 100644
index 0000000000..39431bed38
--- /dev/null
+++ b/crates/ide-db/src/use_trivial_contructor.rs
@@ -0,0 +1,34 @@
+//! Functionality for generating trivial contructors
+
+use hir::StructKind;
+use syntax::ast;
+
+/// given a type return the trivial contructor (if one exists)
+pub fn use_trivial_constructor(
+ db: &crate::RootDatabase,
+ path: ast::Path,
+ ty: &hir::Type,
+) -> Option<ast::Expr> {
+ match ty.as_adt() {
+ Some(hir::Adt::Enum(x)) => {
+ if let &[variant] = &*x.variants(db) {
+ if variant.kind(db) == hir::StructKind::Unit {
+ let path = ast::make::path_qualified(
+ path,
+ syntax::ast::make::path_segment(ast::make::name_ref(
+ &variant.name(db).to_smol_str(),
+ )),
+ );
+
+ return Some(syntax::ast::make::expr_path(path));
+ }
+ }
+ }
+ Some(hir::Adt::Struct(x)) if x.kind(db) == StructKind::Unit => {
+ return Some(syntax::ast::make::expr_path(path));
+ }
+ _ => {}
+ }
+
+ None
+}
diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs
index 891547aaef..30f903af50 100644
--- a/crates/ide-diagnostics/src/handlers/missing_fields.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs
@@ -3,7 +3,10 @@ use hir::{
db::{AstDatabase, HirDatabase},
known, AssocItem, HirDisplay, InFile, Type,
};
-use ide_db::{assists::Assist, famous_defs::FamousDefs, source_change::SourceChange, FxHashMap};
+use ide_db::{
+ assists::Assist, famous_defs::FamousDefs, imports::import_assets::item_for_path_search,
+ source_change::SourceChange, use_trivial_contructor::use_trivial_constructor, FxHashMap,
+};
use stdx::format_to;
use syntax::{
algo,
@@ -55,6 +58,11 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
let root = ctx.sema.db.parse_or_expand(d.file)?;
+ let current_module = match &d.field_list_parent {
+ Either::Left(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
+ Either::Right(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
+ };
+
let build_text_edit = |parent_syntax, new_syntax: &SyntaxNode, old_syntax| {
let edit = {
let mut builder = TextEdit::builder();
@@ -110,7 +118,26 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
Some(generate_fill_expr(ty))
}
} else {
- Some(generate_fill_expr(ty))
+ let expr = (|| -> Option<ast::Expr> {
+ let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
+
+ let type_path = current_module?.find_use_path(
+ ctx.sema.db,
+ item_for_path_search(ctx.sema.db, item_in_ns)?,
+ )?;
+
+ use_trivial_constructor(
+ &ctx.sema.db,
+ ide_db::helpers::mod_path_to_ast(&type_path),
+ &ty,
+ )
+ })();
+
+ if expr.is_some() {
+ expr
+ } else {
+ Some(generate_fill_expr(ty))
+ }
};
let field = make::record_expr_field(
make::name_ref(&f.name(ctx.sema.db).to_smol_str()),
@@ -319,6 +346,92 @@ fn test_fn() {
}
#[test]
+ fn test_fill_struct_zst_fields() {
+ check_fix(
+ r#"
+struct Empty;
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct {$0};
+}
+"#,
+ r#"
+struct Empty;
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct { one: 0, two: Empty };
+}
+"#,
+ );
+ check_fix(
+ r#"
+enum Empty { Foo };
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct {$0};
+}
+"#,
+ r#"
+enum Empty { Foo };
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct { one: 0, two: Empty::Foo };
+}
+"#,
+ );
+
+ // make sure the assist doesn't fill non Unit variants
+ check_fix(
+ r#"
+struct Empty {};
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct {$0};
+}
+"#,
+ r#"
+struct Empty {};
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct { one: 0, two: todo!() };
+}
+"#,
+ );
+ check_fix(
+ r#"
+enum Empty { Foo {} };
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct {$0};
+}
+"#,
+ r#"
+enum Empty { Foo {} };
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct { one: 0, two: todo!() };
+}
+"#,
+ );
+ }
+
+ #[test]
fn test_fill_struct_fields_self() {
check_fix(
r#"