]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/diagnostics/missing_fields.rs
internal: use cov-mark rather than bailing out diagnostic
[rust.git] / crates / ide / src / diagnostics / missing_fields.rs
1 use either::Either;
2 use hir::{db::AstDatabase, InFile};
3 use ide_assists::Assist;
4 use ide_db::source_change::SourceChange;
5 use stdx::format_to;
6 use syntax::{algo, ast::make, AstNode, SyntaxNodePtr};
7 use text_edit::TextEdit;
8
9 use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
10
11 // Diagnostic: missing-fields
12 //
13 // This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
14 //
15 // Example:
16 //
17 // ```rust
18 // struct A { a: u8, b: u8 }
19 //
20 // let a = A { a: 10 };
21 // ```
22 pub(super) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
23     let mut message = String::from("Missing structure fields:\n");
24     for field in &d.missed_fields {
25         format_to!(message, "- {}\n", field);
26     }
27
28     let ptr = InFile::new(
29         d.file,
30         d.field_list_parent_path
31             .clone()
32             .map(SyntaxNodePtr::from)
33             .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
34     );
35
36     Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
37         .with_fixes(fixes(ctx, d))
38 }
39
40 fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
41     // Note that although we could add a diagnostics to
42     // fill the missing tuple field, e.g :
43     // `struct A(usize);`
44     // `let a = A { 0: () }`
45     // but it is uncommon usage and it should not be encouraged.
46     if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
47         return None;
48     }
49
50     let root = ctx.sema.db.parse_or_expand(d.file)?;
51     let field_list_parent = match &d.field_list_parent {
52         Either::Left(record_expr) => record_expr.to_node(&root),
53         // FIXE: patterns should be fixable as well.
54         Either::Right(_) => return None,
55     };
56     let old_field_list = field_list_parent.record_expr_field_list()?;
57     let new_field_list = old_field_list.clone_for_update();
58     for f in d.missed_fields.iter() {
59         let field =
60             make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()))
61                 .clone_for_update();
62         new_field_list.add_field(field);
63     }
64
65     let edit = {
66         let mut builder = TextEdit::builder();
67         algo::diff(old_field_list.syntax(), new_field_list.syntax()).into_text_edit(&mut builder);
68         builder.finish()
69     };
70     Some(vec![fix(
71         "fill_missing_fields",
72         "Fill struct fields",
73         SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit),
74         ctx.sema.original_range(field_list_parent.syntax()).range,
75     )])
76 }
77
78 #[cfg(test)]
79 mod tests {
80     use crate::diagnostics::tests::{check_diagnostics, check_fix};
81
82     #[test]
83     fn missing_record_pat_field_diagnostic() {
84         check_diagnostics(
85             r#"
86 struct S { foo: i32, bar: () }
87 fn baz(s: S) {
88     let S { foo: _ } = s;
89       //^ Missing structure fields:
90       //| - bar
91 }
92 "#,
93         );
94     }
95
96     #[test]
97     fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
98         check_diagnostics(
99             r"
100 struct S { foo: i32, bar: () }
101 fn baz(s: S) -> i32 {
102     match s {
103         S { foo, .. } => foo,
104     }
105 }
106 ",
107         )
108     }
109
110     #[test]
111     fn missing_record_pat_field_box() {
112         check_diagnostics(
113             r"
114 struct S { s: Box<u32> }
115 fn x(a: S) {
116     let S { box s } = a;
117 }
118 ",
119         )
120     }
121
122     #[test]
123     fn missing_record_pat_field_ref() {
124         check_diagnostics(
125             r"
126 struct S { s: u32 }
127 fn x(a: S) {
128     let S { ref s } = a;
129 }
130 ",
131         )
132     }
133
134     #[test]
135     fn range_mapping_out_of_macros() {
136         // FIXME: this is very wrong, but somewhat tricky to fix.
137         check_fix(
138             r#"
139 fn some() {}
140 fn items() {}
141 fn here() {}
142
143 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
144
145 fn main() {
146     let _x = id![Foo { a: $042 }];
147 }
148
149 pub struct Foo { pub a: i32, pub b: i32 }
150 "#,
151             r#"
152 fn some(, b: () ) {}
153 fn items() {}
154 fn here() {}
155
156 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
157
158 fn main() {
159     let _x = id![Foo { a: 42 }];
160 }
161
162 pub struct Foo { pub a: i32, pub b: i32 }
163 "#,
164         );
165     }
166
167     #[test]
168     fn test_fill_struct_fields_empty() {
169         check_fix(
170             r#"
171 struct TestStruct { one: i32, two: i64 }
172
173 fn test_fn() {
174     let s = TestStruct {$0};
175 }
176 "#,
177             r#"
178 struct TestStruct { one: i32, two: i64 }
179
180 fn test_fn() {
181     let s = TestStruct { one: (), two: () };
182 }
183 "#,
184         );
185     }
186
187     #[test]
188     fn test_fill_struct_fields_self() {
189         check_fix(
190             r#"
191 struct TestStruct { one: i32 }
192
193 impl TestStruct {
194     fn test_fn() { let s = Self {$0}; }
195 }
196 "#,
197             r#"
198 struct TestStruct { one: i32 }
199
200 impl TestStruct {
201     fn test_fn() { let s = Self { one: () }; }
202 }
203 "#,
204         );
205     }
206
207     #[test]
208     fn test_fill_struct_fields_enum() {
209         check_fix(
210             r#"
211 enum Expr {
212     Bin { lhs: Box<Expr>, rhs: Box<Expr> }
213 }
214
215 impl Expr {
216     fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
217         Expr::Bin {$0 }
218     }
219 }
220 "#,
221             r#"
222 enum Expr {
223     Bin { lhs: Box<Expr>, rhs: Box<Expr> }
224 }
225
226 impl Expr {
227     fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
228         Expr::Bin { lhs: (), rhs: () }
229     }
230 }
231 "#,
232         );
233     }
234
235     #[test]
236     fn test_fill_struct_fields_partial() {
237         check_fix(
238             r#"
239 struct TestStruct { one: i32, two: i64 }
240
241 fn test_fn() {
242     let s = TestStruct{ two: 2$0 };
243 }
244 "#,
245             r"
246 struct TestStruct { one: i32, two: i64 }
247
248 fn test_fn() {
249     let s = TestStruct{ two: 2, one: () };
250 }
251 ",
252         );
253     }
254
255     #[test]
256     fn test_fill_struct_fields_raw_ident() {
257         check_fix(
258             r#"
259 struct TestStruct { r#type: u8 }
260
261 fn test_fn() {
262     TestStruct { $0 };
263 }
264 "#,
265             r"
266 struct TestStruct { r#type: u8 }
267
268 fn test_fn() {
269     TestStruct { r#type: ()  };
270 }
271 ",
272         );
273     }
274
275     #[test]
276     fn test_fill_struct_fields_no_diagnostic() {
277         check_diagnostics(
278             r#"
279 struct TestStruct { one: i32, two: i64 }
280
281 fn test_fn() {
282     let one = 1;
283     let s = TestStruct{ one, two: 2 };
284 }
285         "#,
286         );
287     }
288
289     #[test]
290     fn test_fill_struct_fields_no_diagnostic_on_spread() {
291         check_diagnostics(
292             r#"
293 struct TestStruct { one: i32, two: i64 }
294
295 fn test_fn() {
296     let one = 1;
297     let s = TestStruct{ ..a };
298 }
299 "#,
300         );
301     }
302
303     #[test]
304     fn test_fill_struct_fields_blank_line() {
305         check_fix(
306             r#"
307 struct S { a: (), b: () }
308
309 fn f() {
310     S {
311         $0
312     };
313 }
314 "#,
315             r#"
316 struct S { a: (), b: () }
317
318 fn f() {
319     S {
320         a: (),
321         b: (),
322     };
323 }
324 "#,
325         );
326     }
327 }