diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-05-06 15:27:34 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-05-06 15:27:34 +0100 |
commit | ef782adc293deb287128f005dbab2038ba3ccdc1 (patch) | |
tree | 6e451b71b482e75a5a30548ab8f769c5ec17864c | |
parent | 32db5884ada59c72aa7ab9f88910ef7c8f882e7d (diff) | |
parent | 12f8472d2800b2d7c05cb1fc466c80072ed8e283 (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.rs | 5 | ||||
-rw-r--r-- | crates/ra_assists/src/fill_struct_fields.rs | 226 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir/src/code_model_api.rs | 11 | ||||
-rw-r--r-- | crates/ra_hir/src/diagnostics.rs | 24 | ||||
-rw-r--r-- | crates/ra_hir/src/expr.rs | 16 | ||||
-rw-r--r-- | crates/ra_hir/src/expr/validation.rs | 92 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/tests.rs | 1 | ||||
-rw-r--r-- | crates/ra_ide_api/src/diagnostics.rs | 123 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/generated.rs | 8 | ||||
-rw-r--r-- | crates/ra_syntax/src/grammar.ron | 7 |
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; | |||
4 | use ra_text_edit::TextEditBuilder; | 4 | use ra_text_edit::TextEditBuilder; |
5 | use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; | 5 | use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; |
6 | use ra_fmt::leading_indent; | 6 | use ra_fmt::leading_indent; |
7 | use hir::Name; | ||
7 | 8 | ||
8 | pub struct AstEditor<N: AstNode> { | 9 | pub 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 | ||
237 | impl AstBuilder<ast::NamedField> { | 238 | impl 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 @@ | |||
1 | use hir::{AdtDef, db::HirDatabase}; | ||
2 | |||
3 | use ra_syntax::ast::{self, AstNode}; | ||
4 | |||
5 | use crate::{AssistCtx, Assist, AssistId, ast_editor::{AstEditor, AstBuilder}}; | ||
6 | |||
7 | pub(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)] | ||
56 | mod 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; | |||
93 | mod flip_binexpr; | 93 | mod flip_binexpr; |
94 | mod change_visibility; | 94 | mod change_visibility; |
95 | mod fill_match_arms; | 95 | mod fill_match_arms; |
96 | mod fill_struct_fields; | ||
97 | mod introduce_variable; | 96 | mod introduce_variable; |
98 | mod inline_local_variable; | 97 | mod inline_local_variable; |
99 | mod replace_if_let_with_match; | 98 | mod 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)] |
432 | pub enum DefWithBody { | 432 | pub enum DefWithBody { |
433 | Function(Function), | 433 | Function(Function), |
434 | Const(Const), | ||
435 | Static(Static), | 434 | Static(Static), |
435 | Const(Const), | ||
436 | } | 436 | } |
437 | 437 | ||
438 | impl_froms!(DefWithBody: Function, Const, Static); | 438 | impl_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}; | |||
3 | use ra_syntax::{SyntaxNodePtr, TreeArc, AstPtr, TextRange, ast, SyntaxNode}; | 3 | use ra_syntax::{SyntaxNodePtr, TreeArc, AstPtr, TextRange, ast, SyntaxNode}; |
4 | use relative_path::RelativePathBuf; | 4 | use relative_path::RelativePathBuf; |
5 | 5 | ||
6 | use crate::{HirFileId, HirDatabase}; | 6 | use 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)] | ||
118 | pub struct MissingFields { | ||
119 | pub file: HirFileId, | ||
120 | pub field_list: AstPtr<ast::NamedFieldList>, | ||
121 | pub missed_fields: Vec<Name>, | ||
122 | } | ||
123 | |||
124 | impl 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 | |||
19 | pub use self::scope::ExprScopes; | 19 | pub use self::scope::ExprScopes; |
20 | 20 | ||
21 | pub(crate) mod scope; | 21 | pub(crate) mod scope; |
22 | pub(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)] |
24 | pub struct ExprId(RawId); | 25 | pub 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 @@ | |||
1 | use std::sync::Arc; | ||
2 | use rustc_hash::FxHashSet; | ||
3 | |||
4 | use ra_syntax::ast::{AstNode, StructLit}; | ||
5 | |||
6 | use crate::{ | ||
7 | expr::AstPtr, | ||
8 | HirDatabase, | ||
9 | Function, | ||
10 | Name, | ||
11 | diagnostics::{DiagnosticSink, MissingFields}, | ||
12 | adt::AdtDef, | ||
13 | Path, | ||
14 | ty::InferenceResult | ||
15 | }; | ||
16 | use super::{Expr, StructLitField, ExprId}; | ||
17 | |||
18 | pub(crate) struct ExprValidator<'a, 'b: 'a> { | ||
19 | func: Function, | ||
20 | infer: Arc<InferenceResult>, | ||
21 | sink: &'a mut DiagnosticSink<'b>, | ||
22 | } | ||
23 | |||
24 | impl<'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}}; | |||
5 | use ra_db::SourceDatabase; | 5 | use ra_db::SourceDatabase; |
6 | use ra_syntax::{ | 6 | use 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 | }; |
10 | use ra_assists::ast_editor::{AstEditor, AstBuilder}; | ||
10 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 11 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
11 | use ra_prof::profile; | 12 | use 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"], |