aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-05-06 15:27:34 +0100
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-05-06 15:27:34 +0100
commitef782adc293deb287128f005dbab2038ba3ccdc1 (patch)
tree6e451b71b482e75a5a30548ab8f769c5ec17864c
parent32db5884ada59c72aa7ab9f88910ef7c8f882e7d (diff)
parent12f8472d2800b2d7c05cb1fc466c80072ed8e283 (diff)
Merge #1163
1163: fill struct fields diagnostic r=matklad a=pasa implementation of #1095 Co-authored-by: Sergey Parilin <[email protected]>
-rw-r--r--crates/ra_assists/src/ast_editor.rs5
-rw-r--r--crates/ra_assists/src/fill_struct_fields.rs226
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_hir/src/code_model_api.rs11
-rw-r--r--crates/ra_hir/src/diagnostics.rs24
-rw-r--r--crates/ra_hir/src/expr.rs16
-rw-r--r--crates/ra_hir/src/expr/validation.rs92
-rw-r--r--crates/ra_hir/src/ty/tests.rs1
-rw-r--r--crates/ra_ide_api/src/diagnostics.rs123
-rw-r--r--crates/ra_syntax/src/ast/generated.rs8
-rw-r--r--crates/ra_syntax/src/grammar.ron7
11 files changed, 269 insertions, 246 deletions
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs
index 6854294ae..726e5c0a3 100644
--- a/crates/ra_assists/src/ast_editor.rs
+++ b/crates/ra_assists/src/ast_editor.rs
@@ -4,6 +4,7 @@ use arrayvec::ArrayVec;
4use ra_text_edit::TextEditBuilder; 4use ra_text_edit::TextEditBuilder;
5use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; 5use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction};
6use ra_fmt::leading_indent; 6use ra_fmt::leading_indent;
7use hir::Name;
7 8
8pub struct AstEditor<N: AstNode> { 9pub struct AstEditor<N: AstNode> {
9 original_ast: TreeArc<N>, 10 original_ast: TreeArc<N>,
@@ -235,6 +236,10 @@ pub struct AstBuilder<N: AstNode> {
235} 236}
236 237
237impl AstBuilder<ast::NamedField> { 238impl AstBuilder<ast::NamedField> {
239 pub fn from_name(name: &Name) -> TreeArc<ast::NamedField> {
240 ast_node_from_file_text(&format!("fn f() {{ S {{ {}: (), }} }}", name))
241 }
242
238 fn from_text(text: &str) -> TreeArc<ast::NamedField> { 243 fn from_text(text: &str) -> TreeArc<ast::NamedField> {
239 ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text)) 244 ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text))
240 } 245 }
diff --git a/crates/ra_assists/src/fill_struct_fields.rs b/crates/ra_assists/src/fill_struct_fields.rs
deleted file mode 100644
index 54b70e17d..000000000
--- a/crates/ra_assists/src/fill_struct_fields.rs
+++ /dev/null
@@ -1,226 +0,0 @@
1use hir::{AdtDef, db::HirDatabase};
2
3use ra_syntax::ast::{self, AstNode};
4
5use crate::{AssistCtx, Assist, AssistId, ast_editor::{AstEditor, AstBuilder}};
6
7pub(crate) fn fill_struct_fields(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
8 let struct_lit = ctx.node_at_offset::<ast::StructLit>()?;
9 let named_field_list = struct_lit.named_field_list()?;
10
11 // Collect all fields from struct definition
12 let mut fields = {
13 let analyzer =
14 hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, struct_lit.syntax(), None);
15 let struct_lit_ty = analyzer.type_of(ctx.db, struct_lit.into())?;
16 let struct_def = match struct_lit_ty.as_adt() {
17 Some((AdtDef::Struct(s), _)) => s,
18 _ => return None,
19 };
20 struct_def.fields(ctx.db)
21 };
22
23 // Filter out existing fields
24 for ast_field in named_field_list.fields() {
25 let name_from_ast = ast_field.name_ref()?.text().to_string();
26 fields.retain(|field| field.name(ctx.db).to_string() != name_from_ast);
27 }
28 if fields.is_empty() {
29 return None;
30 }
31
32 let db = ctx.db;
33 ctx.add_action(AssistId("fill_struct_fields"), "fill struct fields", |edit| {
34 let mut ast_editor = AstEditor::new(named_field_list);
35 if named_field_list.fields().count() == 0 && fields.len() > 2 {
36 ast_editor.make_multiline();
37 };
38
39 for field in fields {
40 let field = AstBuilder::<ast::NamedField>::from_pieces(
41 &AstBuilder::<ast::NameRef>::new(&field.name(db).to_string()),
42 Some(&AstBuilder::<ast::Expr>::unit()),
43 );
44 ast_editor.append_field(&field);
45 }
46
47 edit.target(struct_lit.syntax().range());
48 edit.set_cursor(struct_lit.syntax().range().start());
49
50 ast_editor.into_text_edit(edit.text_edit_builder());
51 });
52 ctx.build()
53}
54
55#[cfg(test)]
56mod tests {
57 use crate::helpers::{check_assist, check_assist_target};
58
59 use super::fill_struct_fields;
60
61 #[test]
62 fn fill_struct_fields_empty_body() {
63 check_assist(
64 fill_struct_fields,
65 r#"
66 struct S<'a, D> {
67 a: u32,
68 b: String,
69 c: (i32, i32),
70 d: D,
71 e: &'a str,
72 }
73
74 fn main() {
75 let s = S<|> {}
76 }
77 "#,
78 r#"
79 struct S<'a, D> {
80 a: u32,
81 b: String,
82 c: (i32, i32),
83 d: D,
84 e: &'a str,
85 }
86
87 fn main() {
88 let s = <|>S {
89 a: (),
90 b: (),
91 c: (),
92 d: (),
93 e: (),
94 }
95 }
96 "#,
97 );
98 }
99
100 #[test]
101 fn fill_struct_fields_target() {
102 check_assist_target(
103 fill_struct_fields,
104 r#"
105 struct S<'a, D> {
106 a: u32,
107 b: String,
108 c: (i32, i32),
109 d: D,
110 e: &'a str,
111 }
112
113 fn main() {
114 let s = S<|> {}
115 }
116 "#,
117 "S {}",
118 );
119 }
120
121 #[test]
122 fn fill_struct_fields_preserve_self() {
123 check_assist(
124 fill_struct_fields,
125 r#"
126 struct Foo {
127 foo: u8,
128 bar: String,
129 baz: i128,
130 }
131
132 impl Foo {
133 pub fn new() -> Self {
134 Self <|>{}
135 }
136 }
137 "#,
138 r#"
139 struct Foo {
140 foo: u8,
141 bar: String,
142 baz: i128,
143 }
144
145 impl Foo {
146 pub fn new() -> Self {
147 <|>Self {
148 foo: (),
149 bar: (),
150 baz: (),
151 }
152 }
153 }
154 "#,
155 );
156 }
157
158 #[test]
159 fn fill_struct_fields_partial() {
160 check_assist(
161 fill_struct_fields,
162 r#"
163 struct S<'a, D> {
164 a: u32,
165 b: String,
166 c: (i32, i32),
167 d: D,
168 e: &'a str,
169 }
170
171 fn main() {
172 let s = S {
173 c: (1, 2),
174 e: "foo",<|>
175 }
176 }
177 "#,
178 r#"
179 struct S<'a, D> {
180 a: u32,
181 b: String,
182 c: (i32, i32),
183 d: D,
184 e: &'a str,
185 }
186
187 fn main() {
188 let s = <|>S {
189 c: (1, 2),
190 e: "foo",
191 a: (),
192 b: (),
193 d: (),
194 }
195 }
196 "#,
197 );
198 }
199
200 #[test]
201 fn fill_struct_short() {
202 check_assist(
203 fill_struct_fields,
204 r#"
205 struct S {
206 foo: u32,
207 bar: String,
208 }
209
210 fn main() {
211 let s = S {<|> };
212 }
213 "#,
214 r#"
215 struct S {
216 foo: u32,
217 bar: String,
218 }
219
220 fn main() {
221 let s = <|>S { foo: (), bar: () };
222 }
223 "#,
224 );
225 }
226}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index a2998ae59..ae97a1ab5 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -93,7 +93,6 @@ mod flip_comma;
93mod flip_binexpr; 93mod flip_binexpr;
94mod change_visibility; 94mod change_visibility;
95mod fill_match_arms; 95mod fill_match_arms;
96mod fill_struct_fields;
97mod introduce_variable; 96mod introduce_variable;
98mod inline_local_variable; 97mod inline_local_variable;
99mod replace_if_let_with_match; 98mod replace_if_let_with_match;
@@ -110,7 +109,6 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis
110 add_impl::add_impl, 109 add_impl::add_impl,
111 change_visibility::change_visibility, 110 change_visibility::change_visibility,
112 fill_match_arms::fill_match_arms, 111 fill_match_arms::fill_match_arms,
113 fill_struct_fields::fill_struct_fields,
114 flip_comma::flip_comma, 112 flip_comma::flip_comma,
115 flip_binexpr::flip_binexpr, 113 flip_binexpr::flip_binexpr,
116 introduce_variable::introduce_variable, 114 introduce_variable::introduce_variable,
diff --git a/crates/ra_hir/src/code_model_api.rs b/crates/ra_hir/src/code_model_api.rs
index 9dcae50a5..55e1793c5 100644
--- a/crates/ra_hir/src/code_model_api.rs
+++ b/crates/ra_hir/src/code_model_api.rs
@@ -8,7 +8,7 @@ use crate::{
8 HirDatabase, DefDatabase, 8 HirDatabase, DefDatabase,
9 type_ref::TypeRef, 9 type_ref::TypeRef,
10 nameres::{ModuleScope, Namespace, ImportId, CrateModuleId}, 10 nameres::{ModuleScope, Namespace, ImportId, CrateModuleId},
11 expr::{Body, BodySourceMap}, 11 expr::{Body, BodySourceMap, validation::ExprValidator},
12 ty::{ TraitRef, InferenceResult}, 12 ty::{ TraitRef, InferenceResult},
13 adt::{EnumVariantId, StructFieldId, VariantDef}, 13 adt::{EnumVariantId, StructFieldId, VariantDef},
14 generics::HasGenericParams, 14 generics::HasGenericParams,
@@ -16,7 +16,7 @@ use crate::{
16 ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeAliasId}, 16 ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeAliasId},
17 impl_block::ImplBlock, 17 impl_block::ImplBlock,
18 resolve::Resolver, 18 resolve::Resolver,
19 diagnostics::DiagnosticSink, 19 diagnostics::{DiagnosticSink},
20 traits::{TraitItem, TraitData}, 20 traits::{TraitItem, TraitData},
21}; 21};
22 22
@@ -431,8 +431,8 @@ impl Docs for EnumVariant {
431#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 431#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
432pub enum DefWithBody { 432pub enum DefWithBody {
433 Function(Function), 433 Function(Function),
434 Const(Const),
435 Static(Static), 434 Static(Static),
435 Const(Const),
436} 436}
437 437
438impl_froms!(DefWithBody: Function, Const, Static); 438impl_froms!(DefWithBody: Function, Const, Static);
@@ -562,7 +562,10 @@ impl Function {
562 } 562 }
563 563
564 pub fn diagnostics(&self, db: &impl HirDatabase, sink: &mut DiagnosticSink) { 564 pub fn diagnostics(&self, db: &impl HirDatabase, sink: &mut DiagnosticSink) {
565 self.infer(db).add_diagnostics(db, *self, sink); 565 let infer = self.infer(db);
566 infer.add_diagnostics(db, *self, sink);
567 let mut validator = ExprValidator::new(*self, infer, sink);
568 validator.validate_body(db);
566 } 569 }
567} 570}
568 571
diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs
index d6a51b833..61cd9d6b1 100644
--- a/crates/ra_hir/src/diagnostics.rs
+++ b/crates/ra_hir/src/diagnostics.rs
@@ -3,7 +3,7 @@ use std::{fmt, any::Any};
3use ra_syntax::{SyntaxNodePtr, TreeArc, AstPtr, TextRange, ast, SyntaxNode}; 3use ra_syntax::{SyntaxNodePtr, TreeArc, AstPtr, TextRange, ast, SyntaxNode};
4use relative_path::RelativePathBuf; 4use relative_path::RelativePathBuf;
5 5
6use crate::{HirFileId, HirDatabase}; 6use crate::{HirFileId, HirDatabase, Name};
7 7
8/// Diagnostic defines hir API for errors and warnings. 8/// Diagnostic defines hir API for errors and warnings.
9/// 9///
@@ -113,3 +113,25 @@ impl Diagnostic for UnresolvedModule {
113 self 113 self
114 } 114 }
115} 115}
116
117#[derive(Debug)]
118pub struct MissingFields {
119 pub file: HirFileId,
120 pub field_list: AstPtr<ast::NamedFieldList>,
121 pub missed_fields: Vec<Name>,
122}
123
124impl Diagnostic for MissingFields {
125 fn message(&self) -> String {
126 "fill structure fields".to_string()
127 }
128 fn file(&self) -> HirFileId {
129 self.file
130 }
131 fn syntax_node_ptr(&self) -> SyntaxNodePtr {
132 self.field_list.into()
133 }
134 fn as_any(&self) -> &(dyn Any + Send + 'static) {
135 self
136 }
137}
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs
index 692da2895..480eaf171 100644
--- a/crates/ra_hir/src/expr.rs
+++ b/crates/ra_hir/src/expr.rs
@@ -19,6 +19,7 @@ use crate::{path::GenericArgs, ty::primitive::{IntTy, UncertainIntTy, FloatTy, U
19pub use self::scope::ExprScopes; 19pub use self::scope::ExprScopes;
20 20
21pub(crate) mod scope; 21pub(crate) mod scope;
22pub(crate) mod validation;
22 23
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct ExprId(RawId); 25pub struct ExprId(RawId);
@@ -670,8 +671,9 @@ where
670 ast::ExprKind::StructLit(e) => { 671 ast::ExprKind::StructLit(e) => {
671 let path = e.path().and_then(Path::from_ast); 672 let path = e.path().and_then(Path::from_ast);
672 let mut field_ptrs = Vec::new(); 673 let mut field_ptrs = Vec::new();
673 let fields = if let Some(nfl) = e.named_field_list() { 674 let struct_lit = if let Some(nfl) = e.named_field_list() {
674 nfl.fields() 675 let fields = nfl
676 .fields()
675 .inspect(|field| field_ptrs.push(AstPtr::new(*field))) 677 .inspect(|field| field_ptrs.push(AstPtr::new(*field)))
676 .map(|field| StructLitField { 678 .map(|field| StructLitField {
677 name: field 679 name: field
@@ -694,12 +696,14 @@ where
694 self.exprs.alloc(Expr::Missing) 696 self.exprs.alloc(Expr::Missing)
695 }, 697 },
696 }) 698 })
697 .collect() 699 .collect();
700 let spread = nfl.spread().map(|s| self.collect_expr(s));
701 Expr::StructLit { path, fields, spread }
698 } else { 702 } else {
699 Vec::new() 703 Expr::StructLit { path, fields: Vec::new(), spread: None }
700 }; 704 };
701 let spread = e.spread().map(|s| self.collect_expr(s)); 705
702 let res = self.alloc_expr(Expr::StructLit { path, fields, spread }, syntax_ptr); 706 let res = self.alloc_expr(struct_lit, syntax_ptr);
703 for (i, ptr) in field_ptrs.into_iter().enumerate() { 707 for (i, ptr) in field_ptrs.into_iter().enumerate() {
704 self.source_map.field_map.insert((res, i), ptr); 708 self.source_map.field_map.insert((res, i), ptr);
705 } 709 }
diff --git a/crates/ra_hir/src/expr/validation.rs b/crates/ra_hir/src/expr/validation.rs
new file mode 100644
index 000000000..fd4907313
--- /dev/null
+++ b/crates/ra_hir/src/expr/validation.rs
@@ -0,0 +1,92 @@
1use std::sync::Arc;
2use rustc_hash::FxHashSet;
3
4use ra_syntax::ast::{AstNode, StructLit};
5
6use crate::{
7 expr::AstPtr,
8 HirDatabase,
9 Function,
10 Name,
11 diagnostics::{DiagnosticSink, MissingFields},
12 adt::AdtDef,
13 Path,
14 ty::InferenceResult
15};
16use super::{Expr, StructLitField, ExprId};
17
18pub(crate) struct ExprValidator<'a, 'b: 'a> {
19 func: Function,
20 infer: Arc<InferenceResult>,
21 sink: &'a mut DiagnosticSink<'b>,
22}
23
24impl<'a, 'b> ExprValidator<'a, 'b> {
25 pub(crate) fn new(
26 func: Function,
27 infer: Arc<InferenceResult>,
28 sink: &'a mut DiagnosticSink<'b>,
29 ) -> ExprValidator<'a, 'b> {
30 ExprValidator { func, infer, sink }
31 }
32
33 pub(crate) fn validate_body(&mut self, db: &impl HirDatabase) {
34 let body = self.func.body(db);
35 for e in body.exprs() {
36 match e {
37 (id, Expr::StructLit { path, fields, spread }) => {
38 self.validate_struct_literal(id, path, fields, spread, db)
39 }
40 _ => (),
41 }
42 }
43 }
44
45 fn validate_struct_literal(
46 &mut self,
47 id: ExprId,
48 _path: &Option<Path>,
49 fields: &Vec<StructLitField>,
50 spread: &Option<ExprId>,
51 db: &impl HirDatabase,
52 ) {
53 if let Some(_) = spread {
54 return;
55 }
56 let lit_fields: FxHashSet<_> = fields.into_iter().map(|f| &f.name).collect();
57 let struct_ty = &self.infer[id];
58 if let Some((AdtDef::Struct(s), _)) = struct_ty.as_adt() {
59 let missed_fields: Vec<Name> = s
60 .fields(db)
61 .iter()
62 .filter_map(|f| {
63 let name = f.name(db);
64 if lit_fields.contains(&name) {
65 None
66 } else {
67 Some(name)
68 }
69 })
70 .collect();
71 if missed_fields.is_empty() {
72 return;
73 }
74 let source_map = self.func.body_source_map(db);
75 let file_id = self.func.source(db).0;
76 let source_file = db.parse(file_id.original_file(db));
77 if let Some(field_list_node) = source_map
78 .expr_syntax(id)
79 .map(|ptr| ptr.to_node(&source_file))
80 .and_then(StructLit::cast)
81 .and_then(|lit| lit.named_field_list())
82 {
83 let field_list_ptr = AstPtr::new(field_list_node);
84 self.sink.push(MissingFields {
85 file: file_id,
86 field_list: field_list_ptr,
87 missed_fields,
88 })
89 }
90 }
91 }
92}
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index 0aecde37c..a38fe35c7 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -2662,6 +2662,7 @@ fn no_such_field_diagnostics() {
2662 2662
2663 assert_snapshot_matches!(diagnostics, @r###" 2663 assert_snapshot_matches!(diagnostics, @r###"
2664"baz: 62": no such field 2664"baz: 62": no such field
2665"{\n foo: 92,\n baz: 62,\n }": fill structure fields
2665"### 2666"###
2666 ); 2667 );
2667} 2668}
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs
index b27cb690a..855a3ff0f 100644
--- a/crates/ra_ide_api/src/diagnostics.rs
+++ b/crates/ra_ide_api/src/diagnostics.rs
@@ -5,8 +5,9 @@ use hir::{source_binder, diagnostics::{Diagnostic as _, DiagnosticSink}};
5use ra_db::SourceDatabase; 5use ra_db::SourceDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 Location, SourceFile, SyntaxKind, TextRange, SyntaxNode, 7 Location, SourceFile, SyntaxKind, TextRange, SyntaxNode,
8 ast::{self, AstNode}, 8 ast::{self, AstNode, NamedFieldList, NamedField},
9}; 9};
10use ra_assists::ast_editor::{AstEditor, AstBuilder};
10use ra_text_edit::{TextEdit, TextEditBuilder}; 11use ra_text_edit::{TextEdit, TextEditBuilder};
11use ra_prof::profile; 12use ra_prof::profile;
12 13
@@ -48,6 +49,27 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
48 severity: Severity::Error, 49 severity: Severity::Error,
49 fix: Some(fix), 50 fix: Some(fix),
50 }) 51 })
52 })
53 .on::<hir::diagnostics::MissingFields, _>(|d| {
54 let file_id = d.file().original_file(db);
55 let source_file = db.parse(file_id);
56 let syntax_node = d.syntax_node_ptr();
57 let node = NamedFieldList::cast(syntax_node.to_node(&source_file)).unwrap();
58 let mut ast_editor = AstEditor::new(node);
59 for f in d.missed_fields.iter() {
60 ast_editor.append_field(&AstBuilder::<NamedField>::from_name(f));
61 }
62
63 let mut builder = TextEditBuilder::default();
64 ast_editor.into_text_edit(&mut builder);
65 let fix =
66 SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish());
67 res.borrow_mut().push(Diagnostic {
68 range: d.highlight_range(),
69 message: d.message(),
70 severity: Severity::Error,
71 fix: Some(fix),
72 })
51 }); 73 });
52 if let Some(m) = source_binder::module_from_file_id(db, file_id) { 74 if let Some(m) = source_binder::module_from_file_id(db, file_id) {
53 m.diagnostics(db, &mut sink); 75 m.diagnostics(db, &mut sink);
@@ -187,6 +209,105 @@ mod tests {
187 assert_eq_text!(after, &actual); 209 assert_eq_text!(after, &actual);
188 } 210 }
189 211
212 fn check_apply_diagnostic_fix(before: &str, after: &str) {
213 let (analysis, file_id) = single_file(before);
214 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
215 let mut fix = diagnostic.fix.unwrap();
216 let edit = fix.source_file_edits.pop().unwrap().edit;
217 let actual = edit.apply(&before);
218 assert_eq_text!(after, &actual);
219 }
220
221 fn check_no_diagnostic(content: &str) {
222 let (analysis, file_id) = single_file(content);
223 let diagnostics = analysis.diagnostics(file_id).unwrap();
224 assert_eq!(diagnostics.len(), 0);
225 }
226
227 #[test]
228 fn test_fill_struct_fields_empty() {
229 let before = r"
230 struct TestStruct {
231 one: i32,
232 two: i64,
233 }
234
235 fn test_fn() {
236 let s = TestStruct{};
237 }
238 ";
239 let after = r"
240 struct TestStruct {
241 one: i32,
242 two: i64,
243 }
244
245 fn test_fn() {
246 let s = TestStruct{ one: (), two: ()};
247 }
248 ";
249 check_apply_diagnostic_fix(before, after);
250 }
251
252 #[test]
253 fn test_fill_struct_fields_partial() {
254 let before = r"
255 struct TestStruct {
256 one: i32,
257 two: i64,
258 }
259
260 fn test_fn() {
261 let s = TestStruct{ two: 2 };
262 }
263 ";
264 let after = r"
265 struct TestStruct {
266 one: i32,
267 two: i64,
268 }
269
270 fn test_fn() {
271 let s = TestStruct{ two: 2, one: () };
272 }
273 ";
274 check_apply_diagnostic_fix(before, after);
275 }
276
277 #[test]
278 fn test_fill_struct_fields_no_diagnostic() {
279 let content = r"
280 struct TestStruct {
281 one: i32,
282 two: i64,
283 }
284
285 fn test_fn() {
286 let one = 1;
287 let s = TestStruct{ one, two: 2 };
288 }
289 ";
290
291 check_no_diagnostic(content);
292 }
293
294 #[test]
295 fn test_fill_struct_fields_no_diagnostic_on_spread() {
296 let content = r"
297 struct TestStruct {
298 one: i32,
299 two: i64,
300 }
301
302 fn test_fn() {
303 let one = 1;
304 let s = TestStruct{ ..a };
305 }
306 ";
307
308 check_no_diagnostic(content);
309 }
310
190 #[test] 311 #[test]
191 fn test_unresolved_module_diagnostic() { 312 fn test_unresolved_module_diagnostic() {
192 let (analysis, file_id) = single_file("mod foo;"); 313 let (analysis, file_id) = single_file("mod foo;");
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index 89d3a35c5..e73fe22e9 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -2371,6 +2371,10 @@ impl NamedFieldList {
2371 pub fn fields(&self) -> impl Iterator<Item = &NamedField> { 2371 pub fn fields(&self) -> impl Iterator<Item = &NamedField> {
2372 super::children(self) 2372 super::children(self)
2373 } 2373 }
2374
2375 pub fn spread(&self) -> Option<&Expr> {
2376 super::child_opt(self)
2377 }
2374} 2378}
2375 2379
2376// NeverType 2380// NeverType
@@ -3564,10 +3568,6 @@ impl StructLit {
3564 pub fn named_field_list(&self) -> Option<&NamedFieldList> { 3568 pub fn named_field_list(&self) -> Option<&NamedFieldList> {
3565 super::child_opt(self) 3569 super::child_opt(self)
3566 } 3570 }
3567
3568 pub fn spread(&self) -> Option<&Expr> {
3569 super::child_opt(self)
3570 }
3571} 3571}
3572 3572
3573// StructPat 3573// StructPat
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index c7abdd6dc..b8665bbc8 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -451,8 +451,11 @@ Grammar(
451 traits: [ "AttrsOwner" ] 451 traits: [ "AttrsOwner" ]
452 ), 452 ),
453 "MatchGuard": (options: ["Expr"]), 453 "MatchGuard": (options: ["Expr"]),
454 "StructLit": (options: ["Path", "NamedFieldList", ["spread", "Expr"]]), 454 "StructLit": (options: ["Path", "NamedFieldList"]),
455 "NamedFieldList": (collections: [ ["fields", "NamedField"] ]), 455 "NamedFieldList": (
456 collections: [ ["fields", "NamedField"] ],
457 options: [["spread", "Expr"]]
458 ),
456 "NamedField": (options: ["NameRef", "Expr"]), 459 "NamedField": (options: ["NameRef", "Expr"]),
457 "CallExpr": ( 460 "CallExpr": (
458 traits: ["ArgListOwner"], 461 traits: ["ArgListOwner"],