]> git.lizzy.rs Git - rust.git/blob - crates/ide_diagnostics/src/handlers/missing_fields.rs
Merge #11878
[rust.git] / crates / ide_diagnostics / src / handlers / missing_fields.rs
1 use either::Either;
2 use hir::{
3     db::{AstDatabase, HirDatabase},
4     known, AssocItem, HirDisplay, InFile, Type,
5 };
6 use ide_db::{assists::Assist, famous_defs::FamousDefs, source_change::SourceChange};
7 use rustc_hash::FxHashMap;
8 use stdx::format_to;
9 use syntax::{
10     algo,
11     ast::{self, make},
12     AstNode, SyntaxNodePtr,
13 };
14 use text_edit::TextEdit;
15
16 use crate::{fix, Diagnostic, DiagnosticsContext};
17
18 // Diagnostic: missing-fields
19 //
20 // This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
21 //
22 // Example:
23 //
24 // ```rust
25 // struct A { a: u8, b: u8 }
26 //
27 // let a = A { a: 10 };
28 // ```
29 pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
30     let mut message = String::from("missing structure fields:\n");
31     for field in &d.missed_fields {
32         format_to!(message, "- {}\n", field);
33     }
34
35     let ptr = InFile::new(
36         d.file,
37         d.field_list_parent_path
38             .clone()
39             .map(SyntaxNodePtr::from)
40             .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
41     );
42
43     Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
44         .with_fixes(fixes(ctx, d))
45 }
46
47 fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
48     // Note that although we could add a diagnostics to
49     // fill the missing tuple field, e.g :
50     // `struct A(usize);`
51     // `let a = A { 0: () }`
52     // but it is uncommon usage and it should not be encouraged.
53     if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
54         return None;
55     }
56
57     let root = ctx.sema.db.parse_or_expand(d.file)?;
58     let field_list_parent = match &d.field_list_parent {
59         Either::Left(record_expr) => record_expr.to_node(&root),
60         // FIXE: patterns should be fixable as well.
61         Either::Right(_) => return None,
62     };
63     let old_field_list = field_list_parent.record_expr_field_list()?;
64
65     let new_field_list = old_field_list.clone_for_update();
66     let mut locals = FxHashMap::default();
67     ctx.sema.scope(field_list_parent.syntax())?.process_all_names(&mut |name, def| {
68         if let hir::ScopeDef::Local(local) = def {
69             locals.insert(name, local);
70         }
71     });
72     let missing_fields = ctx.sema.record_literal_missing_fields(&field_list_parent);
73
74     let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default {
75         crate::ExprFillDefaultMode::Todo => Some(make::ext::expr_todo()),
76         crate::ExprFillDefaultMode::Default => {
77             let default_constr = get_default_constructor(ctx, d, ty);
78             match default_constr {
79                 Some(default_constr) => Some(default_constr),
80                 _ => Some(make::ext::expr_todo()),
81             }
82         }
83     };
84
85     for (f, ty) in missing_fields.iter() {
86         let field_expr = if let Some(local_candidate) = locals.get(&f.name(ctx.sema.db)) {
87             cov_mark::hit!(field_shorthand);
88             let candidate_ty = local_candidate.ty(ctx.sema.db);
89             if ty.could_unify_with(ctx.sema.db, &candidate_ty) {
90                 None
91             } else {
92                 generate_fill_expr(ty)
93             }
94         } else {
95             generate_fill_expr(ty)
96         };
97         let field =
98             make::record_expr_field(make::name_ref(&f.name(ctx.sema.db).to_smol_str()), field_expr)
99                 .clone_for_update();
100         new_field_list.add_field(field);
101     }
102
103     let mut builder = TextEdit::builder();
104     if d.file.is_macro() {
105         // we can't map the diff up into the macro input unfortunately, as the macro loses all
106         // whitespace information so the diff wouldn't be applicable no matter what
107         // This has the downside that the cursor will be moved in macros by doing it without a diff
108         // but that is a trade off we can make.
109         // FIXE: this also currently discards a lot of whitespace in the input... we really need a formatter here
110         let range = ctx.sema.original_range_opt(old_field_list.syntax())?;
111         builder.replace(range.range, new_field_list.to_string());
112     } else {
113         algo::diff(old_field_list.syntax(), new_field_list.syntax()).into_text_edit(&mut builder);
114     }
115     let edit = builder.finish();
116     Some(vec![fix(
117         "fill_missing_fields",
118         "Fill struct fields",
119         SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit),
120         ctx.sema.original_range(field_list_parent.syntax()).range,
121     )])
122 }
123
124 fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
125     let ty_str = match ty.as_adt() {
126         Some(adt) => adt.name(db).to_string(),
127         None => ty.display_source_code(db, module.into()).ok().unwrap_or_else(|| "_".to_string()),
128     };
129
130     make::ty(&ty_str)
131 }
132
133 fn get_default_constructor(
134     ctx: &DiagnosticsContext<'_>,
135     d: &hir::MissingFields,
136     ty: &Type,
137 ) -> Option<ast::Expr> {
138     if let Some(builtin_ty) = ty.as_builtin() {
139         if builtin_ty.is_int() || builtin_ty.is_uint() {
140             return Some(make::ext::zero_number());
141         }
142         if builtin_ty.is_float() {
143             return Some(make::ext::zero_float());
144         }
145         if builtin_ty.is_char() {
146             return Some(make::ext::empty_char());
147         }
148         if builtin_ty.is_str() {
149             return Some(make::ext::empty_str());
150         }
151     }
152
153     let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
154     let module = krate.root_module(ctx.sema.db);
155
156     // Look for a ::new() associated function
157     let has_new_func = ty
158         .iterate_assoc_items(ctx.sema.db, krate, |assoc_item| {
159             if let AssocItem::Function(func) = assoc_item {
160                 if func.name(ctx.sema.db) == known::new
161                     && func.assoc_fn_params(ctx.sema.db).is_empty()
162                 {
163                     return Some(());
164                 }
165             }
166
167             None
168         })
169         .is_some();
170
171     if has_new_func {
172         Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module)))
173     } else if !ty.is_array()
174         && ty.impls_trait(ctx.sema.db, FamousDefs(&ctx.sema, krate).core_default_Default()?, &[])
175     {
176         Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module)))
177     } else {
178         None
179     }
180 }
181
182 #[cfg(test)]
183 mod tests {
184     use crate::tests::{check_diagnostics, check_fix};
185
186     #[test]
187     fn missing_record_pat_field_diagnostic() {
188         check_diagnostics(
189             r#"
190 struct S { foo: i32, bar: () }
191 fn baz(s: S) {
192     let S { foo: _ } = s;
193       //^ error: missing structure fields:
194       //| - bar
195 }
196 "#,
197         );
198     }
199
200     #[test]
201     fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
202         check_diagnostics(
203             r"
204 struct S { foo: i32, bar: () }
205 fn baz(s: S) -> i32 {
206     match s {
207         S { foo, .. } => foo,
208     }
209 }
210 ",
211         )
212     }
213
214     #[test]
215     fn missing_record_pat_field_box() {
216         check_diagnostics(
217             r"
218 struct S { s: Box<u32> }
219 fn x(a: S) {
220     let S { box s } = a;
221 }
222 ",
223         )
224     }
225
226     #[test]
227     fn missing_record_pat_field_ref() {
228         check_diagnostics(
229             r"
230 struct S { s: u32 }
231 fn x(a: S) {
232     let S { ref s } = a;
233 }
234 ",
235         )
236     }
237
238     #[test]
239     fn range_mapping_out_of_macros() {
240         check_fix(
241             r#"
242 fn some() {}
243 fn items() {}
244 fn here() {}
245
246 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
247
248 fn main() {
249     let _x = id![Foo { a: $042 }];
250 }
251
252 pub struct Foo { pub a: i32, pub b: i32 }
253 "#,
254             r#"
255 fn some() {}
256 fn items() {}
257 fn here() {}
258
259 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
260
261 fn main() {
262     let _x = id![Foo {a:42, b: 0 }];
263 }
264
265 pub struct Foo { pub a: i32, pub b: i32 }
266 "#,
267         );
268     }
269
270     #[test]
271     fn test_fill_struct_fields_empty() {
272         check_fix(
273             r#"
274 struct TestStruct { one: i32, two: i64 }
275
276 fn test_fn() {
277     let s = TestStruct {$0};
278 }
279 "#,
280             r#"
281 struct TestStruct { one: i32, two: i64 }
282
283 fn test_fn() {
284     let s = TestStruct { one: 0, two: 0 };
285 }
286 "#,
287         );
288     }
289
290     #[test]
291     fn test_fill_struct_fields_self() {
292         check_fix(
293             r#"
294 struct TestStruct { one: i32 }
295
296 impl TestStruct {
297     fn test_fn() { let s = Self {$0}; }
298 }
299 "#,
300             r#"
301 struct TestStruct { one: i32 }
302
303 impl TestStruct {
304     fn test_fn() { let s = Self { one: 0 }; }
305 }
306 "#,
307         );
308     }
309
310     #[test]
311     fn test_fill_struct_fields_enum() {
312         check_fix(
313             r#"
314 enum Expr {
315     Bin { lhs: Box<Expr>, rhs: Box<Expr> }
316 }
317
318 impl Expr {
319     fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
320         Expr::Bin {$0 }
321     }
322 }
323 "#,
324             r#"
325 enum Expr {
326     Bin { lhs: Box<Expr>, rhs: Box<Expr> }
327 }
328
329 impl Expr {
330     fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
331         Expr::Bin { lhs, rhs }
332     }
333 }
334 "#,
335         );
336     }
337
338     #[test]
339     fn test_fill_struct_fields_partial() {
340         check_fix(
341             r#"
342 struct TestStruct { one: i32, two: i64 }
343
344 fn test_fn() {
345     let s = TestStruct{ two: 2$0 };
346 }
347 "#,
348             r"
349 struct TestStruct { one: i32, two: i64 }
350
351 fn test_fn() {
352     let s = TestStruct{ two: 2, one: 0 };
353 }
354 ",
355         );
356     }
357
358     #[test]
359     fn test_fill_struct_fields_new() {
360         check_fix(
361             r#"
362 struct TestWithNew(usize);
363 impl TestWithNew {
364     pub fn new() -> Self {
365         Self(0)
366     }
367 }
368 struct TestStruct { one: i32, two: TestWithNew }
369
370 fn test_fn() {
371     let s = TestStruct{ $0 };
372 }
373 "#,
374             r"
375 struct TestWithNew(usize);
376 impl TestWithNew {
377     pub fn new() -> Self {
378         Self(0)
379     }
380 }
381 struct TestStruct { one: i32, two: TestWithNew }
382
383 fn test_fn() {
384     let s = TestStruct{ one: 0, two: TestWithNew::new()  };
385 }
386 ",
387         );
388     }
389
390     #[test]
391     fn test_fill_struct_fields_default() {
392         check_fix(
393             r#"
394 //- minicore: default
395 struct TestWithDefault(usize);
396 impl Default for TestWithDefault {
397     pub fn default() -> Self {
398         Self(0)
399     }
400 }
401 struct TestStruct { one: i32, two: TestWithDefault }
402
403 fn test_fn() {
404     let s = TestStruct{ $0 };
405 }
406 "#,
407             r"
408 struct TestWithDefault(usize);
409 impl Default for TestWithDefault {
410     pub fn default() -> Self {
411         Self(0)
412     }
413 }
414 struct TestStruct { one: i32, two: TestWithDefault }
415
416 fn test_fn() {
417     let s = TestStruct{ one: 0, two: TestWithDefault::default()  };
418 }
419 ",
420         );
421     }
422
423     #[test]
424     fn test_fill_struct_fields_raw_ident() {
425         check_fix(
426             r#"
427 struct TestStruct { r#type: u8 }
428
429 fn test_fn() {
430     TestStruct { $0 };
431 }
432 "#,
433             r"
434 struct TestStruct { r#type: u8 }
435
436 fn test_fn() {
437     TestStruct { r#type: 0  };
438 }
439 ",
440         );
441     }
442
443     #[test]
444     fn test_fill_struct_fields_no_diagnostic() {
445         check_diagnostics(
446             r#"
447 struct TestStruct { one: i32, two: i64 }
448
449 fn test_fn() {
450     let one = 1;
451     let s = TestStruct{ one, two: 2 };
452 }
453         "#,
454         );
455     }
456
457     #[test]
458     fn test_fill_struct_fields_no_diagnostic_on_spread() {
459         check_diagnostics(
460             r#"
461 struct TestStruct { one: i32, two: i64 }
462
463 fn test_fn() {
464     let one = 1;
465     let s = TestStruct{ ..a };
466 }
467 "#,
468         );
469     }
470
471     #[test]
472     fn test_fill_struct_fields_blank_line() {
473         check_fix(
474             r#"
475 struct S { a: (), b: () }
476
477 fn f() {
478     S {
479         $0
480     };
481 }
482 "#,
483             r#"
484 struct S { a: (), b: () }
485
486 fn f() {
487     S {
488         a: todo!(),
489         b: todo!(),
490     };
491 }
492 "#,
493         );
494     }
495
496     #[test]
497     fn test_fill_struct_fields_shorthand() {
498         cov_mark::check!(field_shorthand);
499         check_fix(
500             r#"
501 struct S { a: &'static str, b: i32 }
502
503 fn f() {
504     let a = "hello";
505     let b = 1i32;
506     S {
507         $0
508     };
509 }
510 "#,
511             r#"
512 struct S { a: &'static str, b: i32 }
513
514 fn f() {
515     let a = "hello";
516     let b = 1i32;
517     S {
518         a,
519         b,
520     };
521 }
522 "#,
523         );
524     }
525
526     #[test]
527     fn test_fill_struct_fields_shorthand_ty_mismatch() {
528         check_fix(
529             r#"
530 struct S { a: &'static str, b: i32 }
531
532 fn f() {
533     let a = "hello";
534     let b = 1usize;
535     S {
536         $0
537     };
538 }
539 "#,
540             r#"
541 struct S { a: &'static str, b: i32 }
542
543 fn f() {
544     let a = "hello";
545     let b = 1usize;
546     S {
547         a,
548         b: 0,
549     };
550 }
551 "#,
552         );
553     }
554
555     #[test]
556     fn test_fill_struct_fields_shorthand_unifies() {
557         check_fix(
558             r#"
559 struct S<T> { a: &'static str, b: T }
560
561 fn f() {
562     let a = "hello";
563     let b = 1i32;
564     S {
565         $0
566     };
567 }
568 "#,
569             r#"
570 struct S<T> { a: &'static str, b: T }
571
572 fn f() {
573     let a = "hello";
574     let b = 1i32;
575     S {
576         a,
577         b,
578     };
579 }
580 "#,
581         );
582     }
583
584     #[test]
585     fn import_extern_crate_clash_with_inner_item() {
586         // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
587
588         check_diagnostics(
589             r#"
590 //- /lib.rs crate:lib deps:jwt
591 mod permissions;
592
593 use permissions::jwt;
594
595 fn f() {
596     fn inner() {}
597     jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
598 }
599
600 //- /permissions.rs
601 pub mod jwt  {
602     pub struct Claims {}
603 }
604
605 //- /jwt/lib.rs crate:jwt
606 pub struct Claims {
607     field: u8,
608 }
609         "#,
610         );
611     }
612 }