Skip to content

Commit 01d2517

Browse files
committed
Auto merge of rust-lang#12539 - soruh:instanciate_empty_structs, r=Veykril
Automatically instaciate trivially instaciable structs in "Generate new" and "Fill struct fields" As proposed in rust-lang#12535 this PR changes the "Generate new" and "Fill struct fields" assist/diagnostic to instanciate structs with no fields and enums with a single empty variant. For example: ```rust pub enum Bar { Bar {}, } struct Foo<T> { a: usize, bar: Bar, _phantom: std::marker::PhantomData<T>, } impl<T> Foo<T> { /* generate new */ fn random() -> Self { Self { /* Fill struct fields */ } } } ``` was previously: ```rust impl<T> Foo<T> { fn new(a: usize, bar: Bar, _phantom: std::marker::PhantomData<T>) -> Self { Self { a, bar, _phantom } } fn random() -> Self { Self { a: todo!(), bar: todo!(), _phantom: todo!(), } } } ``` and is now: ```rust impl<T> Foo<T> { fn new(a: usize) -> Self { Self { a, bar: Bar::Bar {}, _phantom: std::marker::PhantomData } } fn random() -> Self { Self { a: todo!(), bar: Bar::Bar {}, _phantom: std::marker::PhantomData, } } } ``` I'd be happy about any suggestions. ## TODO - [x] deduplicate `use_trivial_constructor` (unclear how to do as it's used in two separate crates) - [x] write tests Closes rust-lang#12535
2 parents ac526e0 + a5ad4de commit 01d2517

File tree

4 files changed

+287
-4
lines changed

4 files changed

+287
-4
lines changed

crates/ide-assists/src/handlers/generate_new.rs

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use ide_db::{
2+
imports::import_assets::item_for_path_search, use_trivial_contructor::use_trivial_constructor,
3+
};
14
use itertools::Itertools;
25
use stdx::format_to;
36
use syntax::ast::{self, AstNode, HasName, HasVisibility, StructKind};
@@ -38,6 +41,8 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
3841
// Return early if we've found an existing new fn
3942
let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), "new")?;
4043

44+
let current_module = ctx.sema.scope(strukt.syntax())?.module();
45+
4146
let target = strukt.syntax().text_range();
4247
acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
4348
let mut buf = String::with_capacity(512);
@@ -48,11 +53,50 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
4853

4954
let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
5055

56+
let trivial_constructors = field_list
57+
.fields()
58+
.map(|f| {
59+
let ty = ctx.sema.resolve_type(&f.ty()?)?;
60+
61+
let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
62+
63+
let type_path = current_module
64+
.find_use_path(ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?)?;
65+
66+
let expr = use_trivial_constructor(
67+
&ctx.sema.db,
68+
ide_db::helpers::mod_path_to_ast(&type_path),
69+
&ty,
70+
)?;
71+
72+
Some(format!("{}: {}", f.name()?.syntax(), expr))
73+
})
74+
.collect::<Vec<_>>();
75+
5176
let params = field_list
5277
.fields()
53-
.filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
78+
.enumerate()
79+
.filter_map(|(i, f)| {
80+
if trivial_constructors[i].is_none() {
81+
Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax()))
82+
} else {
83+
None
84+
}
85+
})
86+
.format(", ");
87+
88+
let fields = field_list
89+
.fields()
90+
.enumerate()
91+
.filter_map(|(i, f)| {
92+
let contructor = trivial_constructors[i].clone();
93+
if contructor.is_some() {
94+
contructor
95+
} else {
96+
Some(f.name()?.to_string())
97+
}
98+
})
5499
.format(", ");
55-
let fields = field_list.fields().filter_map(|f| f.name()).format(", ");
56100

57101
format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
58102

@@ -79,6 +123,97 @@ mod tests {
79123

80124
use super::*;
81125

126+
#[test]
127+
fn test_generate_new_with_zst_fields() {
128+
check_assist(
129+
generate_new,
130+
r#"
131+
struct Empty;
132+
133+
struct Foo { empty: Empty $0}
134+
"#,
135+
r#"
136+
struct Empty;
137+
138+
struct Foo { empty: Empty }
139+
140+
impl Foo {
141+
fn $0new() -> Self { Self { empty: Empty } }
142+
}
143+
"#,
144+
);
145+
check_assist(
146+
generate_new,
147+
r#"
148+
struct Empty;
149+
150+
struct Foo { baz: String, empty: Empty $0}
151+
"#,
152+
r#"
153+
struct Empty;
154+
155+
struct Foo { baz: String, empty: Empty }
156+
157+
impl Foo {
158+
fn $0new(baz: String) -> Self { Self { baz, empty: Empty } }
159+
}
160+
"#,
161+
);
162+
check_assist(
163+
generate_new,
164+
r#"
165+
enum Empty { Bar }
166+
167+
struct Foo { empty: Empty $0}
168+
"#,
169+
r#"
170+
enum Empty { Bar }
171+
172+
struct Foo { empty: Empty }
173+
174+
impl Foo {
175+
fn $0new() -> Self { Self { empty: Empty::Bar } }
176+
}
177+
"#,
178+
);
179+
180+
// make sure the assist only works on unit variants
181+
check_assist(
182+
generate_new,
183+
r#"
184+
struct Empty {}
185+
186+
struct Foo { empty: Empty $0}
187+
"#,
188+
r#"
189+
struct Empty {}
190+
191+
struct Foo { empty: Empty }
192+
193+
impl Foo {
194+
fn $0new(empty: Empty) -> Self { Self { empty } }
195+
}
196+
"#,
197+
);
198+
check_assist(
199+
generate_new,
200+
r#"
201+
enum Empty { Bar {} }
202+
203+
struct Foo { empty: Empty $0}
204+
"#,
205+
r#"
206+
enum Empty { Bar {} }
207+
208+
struct Foo { empty: Empty }
209+
210+
impl Foo {
211+
fn $0new(empty: Empty) -> Self { Self { empty } }
212+
}
213+
"#,
214+
);
215+
}
216+
82217
#[test]
83218
fn test_generate_new() {
84219
check_assist(

crates/ide-db/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub mod source_change;
2020
pub mod symbol_index;
2121
pub mod traits;
2222
pub mod ty_filter;
23+
pub mod use_trivial_contructor;
2324

2425
pub mod imports {
2526
pub mod import_assets;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//! Functionality for generating trivial contructors
2+
3+
use hir::StructKind;
4+
use syntax::ast;
5+
6+
/// given a type return the trivial contructor (if one exists)
7+
pub fn use_trivial_constructor(
8+
db: &crate::RootDatabase,
9+
path: ast::Path,
10+
ty: &hir::Type,
11+
) -> Option<ast::Expr> {
12+
match ty.as_adt() {
13+
Some(hir::Adt::Enum(x)) => {
14+
if let &[variant] = &*x.variants(db) {
15+
if variant.kind(db) == hir::StructKind::Unit {
16+
let path = ast::make::path_qualified(
17+
path,
18+
syntax::ast::make::path_segment(ast::make::name_ref(
19+
&variant.name(db).to_smol_str(),
20+
)),
21+
);
22+
23+
return Some(syntax::ast::make::expr_path(path));
24+
}
25+
}
26+
}
27+
Some(hir::Adt::Struct(x)) if x.kind(db) == StructKind::Unit => {
28+
return Some(syntax::ast::make::expr_path(path));
29+
}
30+
_ => {}
31+
}
32+
33+
None
34+
}

crates/ide-diagnostics/src/handlers/missing_fields.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use hir::{
33
db::{AstDatabase, HirDatabase},
44
known, AssocItem, HirDisplay, InFile, Type,
55
};
6-
use ide_db::{assists::Assist, famous_defs::FamousDefs, source_change::SourceChange, FxHashMap};
6+
use ide_db::{
7+
assists::Assist, famous_defs::FamousDefs, imports::import_assets::item_for_path_search,
8+
source_change::SourceChange, use_trivial_contructor::use_trivial_constructor, FxHashMap,
9+
};
710
use stdx::format_to;
811
use syntax::{
912
algo,
@@ -55,6 +58,11 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
5558

5659
let root = ctx.sema.db.parse_or_expand(d.file)?;
5760

61+
let current_module = match &d.field_list_parent {
62+
Either::Left(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
63+
Either::Right(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
64+
};
65+
5866
let build_text_edit = |parent_syntax, new_syntax: &SyntaxNode, old_syntax| {
5967
let edit = {
6068
let mut builder = TextEdit::builder();
@@ -110,7 +118,26 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
110118
Some(generate_fill_expr(ty))
111119
}
112120
} else {
113-
Some(generate_fill_expr(ty))
121+
let expr = (|| -> Option<ast::Expr> {
122+
let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
123+
124+
let type_path = current_module?.find_use_path(
125+
ctx.sema.db,
126+
item_for_path_search(ctx.sema.db, item_in_ns)?,
127+
)?;
128+
129+
use_trivial_constructor(
130+
&ctx.sema.db,
131+
ide_db::helpers::mod_path_to_ast(&type_path),
132+
&ty,
133+
)
134+
})();
135+
136+
if expr.is_some() {
137+
expr
138+
} else {
139+
Some(generate_fill_expr(ty))
140+
}
114141
};
115142
let field = make::record_expr_field(
116143
make::name_ref(&f.name(ctx.sema.db).to_smol_str()),
@@ -318,6 +345,92 @@ fn test_fn() {
318345
);
319346
}
320347

348+
#[test]
349+
fn test_fill_struct_zst_fields() {
350+
check_fix(
351+
r#"
352+
struct Empty;
353+
354+
struct TestStruct { one: i32, two: Empty }
355+
356+
fn test_fn() {
357+
let s = TestStruct {$0};
358+
}
359+
"#,
360+
r#"
361+
struct Empty;
362+
363+
struct TestStruct { one: i32, two: Empty }
364+
365+
fn test_fn() {
366+
let s = TestStruct { one: 0, two: Empty };
367+
}
368+
"#,
369+
);
370+
check_fix(
371+
r#"
372+
enum Empty { Foo };
373+
374+
struct TestStruct { one: i32, two: Empty }
375+
376+
fn test_fn() {
377+
let s = TestStruct {$0};
378+
}
379+
"#,
380+
r#"
381+
enum Empty { Foo };
382+
383+
struct TestStruct { one: i32, two: Empty }
384+
385+
fn test_fn() {
386+
let s = TestStruct { one: 0, two: Empty::Foo };
387+
}
388+
"#,
389+
);
390+
391+
// make sure the assist doesn't fill non Unit variants
392+
check_fix(
393+
r#"
394+
struct Empty {};
395+
396+
struct TestStruct { one: i32, two: Empty }
397+
398+
fn test_fn() {
399+
let s = TestStruct {$0};
400+
}
401+
"#,
402+
r#"
403+
struct Empty {};
404+
405+
struct TestStruct { one: i32, two: Empty }
406+
407+
fn test_fn() {
408+
let s = TestStruct { one: 0, two: todo!() };
409+
}
410+
"#,
411+
);
412+
check_fix(
413+
r#"
414+
enum Empty { Foo {} };
415+
416+
struct TestStruct { one: i32, two: Empty }
417+
418+
fn test_fn() {
419+
let s = TestStruct {$0};
420+
}
421+
"#,
422+
r#"
423+
enum Empty { Foo {} };
424+
425+
struct TestStruct { one: i32, two: Empty }
426+
427+
fn test_fn() {
428+
let s = TestStruct { one: 0, two: todo!() };
429+
}
430+
"#,
431+
);
432+
}
433+
321434
#[test]
322435
fn test_fill_struct_fields_self() {
323436
check_fix(

0 commit comments

Comments
 (0)