• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use either::Either;
2 use hir::{
3     db::{ExpandDatabase, HirDatabase},
4     known, AssocItem, HirDisplay, InFile, Type,
5 };
6 use ide_db::{
7     assists::Assist, famous_defs::FamousDefs, imports::import_assets::item_for_path_search,
8     source_change::SourceChange, use_trivial_constructor::use_trivial_constructor, FxHashMap,
9 };
10 use stdx::format_to;
11 use syntax::{
12     algo,
13     ast::{self, make},
14     AstNode, SyntaxNode, SyntaxNodePtr,
15 };
16 use text_edit::TextEdit;
17 
18 use crate::{fix, Diagnostic, DiagnosticsContext};
19 
20 // Diagnostic: missing-fields
21 //
22 // This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
23 //
24 // Example:
25 //
26 // ```rust
27 // struct A { a: u8, b: u8 }
28 //
29 // let a = A { a: 10 };
30 // ```
missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic31 pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
32     let mut message = String::from("missing structure fields:\n");
33     for field in &d.missed_fields {
34         format_to!(message, "- {}\n", field.display(ctx.sema.db));
35     }
36 
37     let ptr = InFile::new(
38         d.file,
39         d.field_list_parent_path
40             .clone()
41             .map(SyntaxNodePtr::from)
42             .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
43     );
44 
45     Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
46         .with_fixes(fixes(ctx, d))
47 }
48 
fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>>49 fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
50     // Note that although we could add a diagnostics to
51     // fill the missing tuple field, e.g :
52     // `struct A(usize);`
53     // `let a = A { 0: () }`
54     // but it is uncommon usage and it should not be encouraged.
55     if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
56         return None;
57     }
58 
59     let root = ctx.sema.db.parse_or_expand(d.file);
60 
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 
66     let build_text_edit = |parent_syntax, new_syntax: &SyntaxNode, old_syntax| {
67         let edit = {
68             let mut builder = TextEdit::builder();
69             if d.file.is_macro() {
70                 // we can't map the diff up into the macro input unfortunately, as the macro loses all
71                 // whitespace information so the diff wouldn't be applicable no matter what
72                 // This has the downside that the cursor will be moved in macros by doing it without a diff
73                 // but that is a trade off we can make.
74                 // FIXME: this also currently discards a lot of whitespace in the input... we really need a formatter here
75                 let range = ctx.sema.original_range_opt(old_syntax)?;
76                 builder.replace(range.range, new_syntax.to_string());
77             } else {
78                 algo::diff(old_syntax, new_syntax).into_text_edit(&mut builder);
79             }
80             builder.finish()
81         };
82         Some(vec![fix(
83             "fill_missing_fields",
84             "Fill struct fields",
85             SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit),
86             ctx.sema.original_range(parent_syntax).range,
87         )])
88     };
89 
90     match &d.field_list_parent {
91         Either::Left(record_expr) => {
92             let field_list_parent = record_expr.to_node(&root);
93             let missing_fields = ctx.sema.record_literal_missing_fields(&field_list_parent);
94 
95             let mut locals = FxHashMap::default();
96             ctx.sema.scope(field_list_parent.syntax())?.process_all_names(&mut |name, def| {
97                 if let hir::ScopeDef::Local(local) = def {
98                     locals.insert(name, local);
99                 }
100             });
101 
102             let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default {
103                 crate::ExprFillDefaultMode::Todo => make::ext::expr_todo(),
104                 crate::ExprFillDefaultMode::Default => {
105                     get_default_constructor(ctx, d, ty).unwrap_or_else(|| make::ext::expr_todo())
106                 }
107             };
108 
109             let old_field_list = field_list_parent.record_expr_field_list()?;
110             let new_field_list = old_field_list.clone_for_update();
111             for (f, ty) in missing_fields.iter() {
112                 let field_expr = if let Some(local_candidate) = locals.get(&f.name(ctx.sema.db)) {
113                     cov_mark::hit!(field_shorthand);
114                     let candidate_ty = local_candidate.ty(ctx.sema.db);
115                     if ty.could_unify_with(ctx.sema.db, &candidate_ty) {
116                         None
117                     } else {
118                         Some(generate_fill_expr(ty))
119                     }
120                 } else {
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                             ctx.config.prefer_no_std,
128                         )?;
129 
130                         use_trivial_constructor(
131                             ctx.sema.db,
132                             ide_db::helpers::mod_path_to_ast(&type_path),
133                             ty,
134                         )
135                     })();
136 
137                     if expr.is_some() {
138                         expr
139                     } else {
140                         Some(generate_fill_expr(ty))
141                     }
142                 };
143                 let field = make::record_expr_field(
144                     make::name_ref(&f.name(ctx.sema.db).to_smol_str()),
145                     field_expr,
146                 );
147                 new_field_list.add_field(field.clone_for_update());
148             }
149             build_text_edit(
150                 field_list_parent.syntax(),
151                 new_field_list.syntax(),
152                 old_field_list.syntax(),
153             )
154         }
155         Either::Right(record_pat) => {
156             let field_list_parent = record_pat.to_node(&root);
157             let missing_fields = ctx.sema.record_pattern_missing_fields(&field_list_parent);
158 
159             let old_field_list = field_list_parent.record_pat_field_list()?;
160             let new_field_list = old_field_list.clone_for_update();
161             for (f, _) in missing_fields.iter() {
162                 let field = make::record_pat_field_shorthand(make::name_ref(
163                     &f.name(ctx.sema.db).to_smol_str(),
164                 ));
165                 new_field_list.add_field(field.clone_for_update());
166             }
167             build_text_edit(
168                 field_list_parent.syntax(),
169                 new_field_list.syntax(),
170                 old_field_list.syntax(),
171             )
172         }
173     }
174 }
175 
make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type176 fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
177     let ty_str = match ty.as_adt() {
178         Some(adt) => adt.name(db).display(db.upcast()).to_string(),
179         None => {
180             ty.display_source_code(db, module.into(), false).ok().unwrap_or_else(|| "_".to_string())
181         }
182     };
183 
184     make::ty(&ty_str)
185 }
186 
get_default_constructor( ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields, ty: &Type, ) -> Option<ast::Expr>187 fn get_default_constructor(
188     ctx: &DiagnosticsContext<'_>,
189     d: &hir::MissingFields,
190     ty: &Type,
191 ) -> Option<ast::Expr> {
192     if let Some(builtin_ty) = ty.as_builtin() {
193         if builtin_ty.is_int() || builtin_ty.is_uint() {
194             return Some(make::ext::zero_number());
195         }
196         if builtin_ty.is_float() {
197             return Some(make::ext::zero_float());
198         }
199         if builtin_ty.is_char() {
200             return Some(make::ext::empty_char());
201         }
202         if builtin_ty.is_str() {
203             return Some(make::ext::empty_str());
204         }
205         if builtin_ty.is_bool() {
206             return Some(make::ext::default_bool());
207         }
208     }
209 
210     let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
211     let module = krate.root_module(ctx.sema.db);
212 
213     // Look for a ::new() associated function
214     let has_new_func = ty
215         .iterate_assoc_items(ctx.sema.db, krate, |assoc_item| {
216             if let AssocItem::Function(func) = assoc_item {
217                 if func.name(ctx.sema.db) == known::new
218                     && func.assoc_fn_params(ctx.sema.db).is_empty()
219                 {
220                     return Some(());
221                 }
222             }
223 
224             None
225         })
226         .is_some();
227 
228     let famous_defs = FamousDefs(&ctx.sema, krate);
229     if has_new_func {
230         Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module)))
231     } else if ty.as_adt() == famous_defs.core_option_Option()?.ty(ctx.sema.db).as_adt() {
232         Some(make::ext::option_none())
233     } else if !ty.is_array()
234         && ty.impls_trait(ctx.sema.db, famous_defs.core_default_Default()?, &[])
235     {
236         Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module)))
237     } else {
238         None
239     }
240 }
241 
242 #[cfg(test)]
243 mod tests {
244     use crate::tests::{check_diagnostics, check_fix};
245 
246     #[test]
missing_record_pat_field_diagnostic()247     fn missing_record_pat_field_diagnostic() {
248         check_diagnostics(
249             r#"
250 struct S { foo: i32, bar: () }
251 fn baz(s: S) {
252     let S { foo: _ } = s;
253       //^ �� error: missing structure fields:
254       //| - bar
255 }
256 "#,
257         );
258     }
259 
260     #[test]
missing_record_pat_field_no_diagnostic_if_not_exhaustive()261     fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
262         check_diagnostics(
263             r"
264 struct S { foo: i32, bar: () }
265 fn baz(s: S) -> i32 {
266     match s {
267         S { foo, .. } => foo,
268     }
269 }
270 ",
271         )
272     }
273 
274     #[test]
missing_record_pat_field_box()275     fn missing_record_pat_field_box() {
276         check_diagnostics(
277             r"
278 struct S { s: Box<u32> }
279 fn x(a: S) {
280     let S { box s } = a;
281 }
282 ",
283         )
284     }
285 
286     #[test]
missing_record_pat_field_ref()287     fn missing_record_pat_field_ref() {
288         check_diagnostics(
289             r"
290 struct S { s: u32 }
291 fn x(a: S) {
292     let S { ref s } = a;
293 }
294 ",
295         )
296     }
297 
298     #[test]
missing_record_expr_in_assignee_expr()299     fn missing_record_expr_in_assignee_expr() {
300         check_diagnostics(
301             r"
302 struct S { s: usize, t: usize }
303 struct S2 { s: S, t: () }
304 struct T(S);
305 fn regular(a: S) {
306     let s;
307     S { s, .. } = a;
308 }
309 fn nested(a: S2) {
310     let s;
311     S2 { s: S { s, .. }, .. } = a;
312 }
313 fn in_tuple(a: (S,)) {
314     let s;
315     (S { s, .. },) = a;
316 }
317 fn in_array(a: [S;1]) {
318     let s;
319     [S { s, .. },] = a;
320 }
321 fn in_tuple_struct(a: T) {
322     let s;
323     T(S { s, .. }) = a;
324 }
325             ",
326         );
327     }
328 
329     #[test]
range_mapping_out_of_macros()330     fn range_mapping_out_of_macros() {
331         check_fix(
332             r#"
333 fn some() {}
334 fn items() {}
335 fn here() {}
336 
337 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
338 
339 fn main() {
340     let _x = id![Foo { a: $042 }];
341 }
342 
343 pub struct Foo { pub a: i32, pub b: i32 }
344 "#,
345             r#"
346 fn some() {}
347 fn items() {}
348 fn here() {}
349 
350 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
351 
352 fn main() {
353     let _x = id![Foo {a:42, b: 0 }];
354 }
355 
356 pub struct Foo { pub a: i32, pub b: i32 }
357 "#,
358         );
359     }
360 
361     #[test]
test_fill_struct_fields_empty()362     fn test_fill_struct_fields_empty() {
363         check_fix(
364             r#"
365 //- minicore: option
366 struct TestStruct { one: i32, two: i64, three: Option<i32>, four: bool }
367 
368 fn test_fn() {
369     let s = TestStruct {$0};
370 }
371 "#,
372             r#"
373 struct TestStruct { one: i32, two: i64, three: Option<i32>, four: bool }
374 
375 fn test_fn() {
376     let s = TestStruct { one: 0, two: 0, three: None, four: false };
377 }
378 "#,
379         );
380     }
381 
382     #[test]
test_fill_struct_zst_fields()383     fn test_fill_struct_zst_fields() {
384         check_fix(
385             r#"
386 struct Empty;
387 
388 struct TestStruct { one: i32, two: Empty }
389 
390 fn test_fn() {
391     let s = TestStruct {$0};
392 }
393 "#,
394             r#"
395 struct Empty;
396 
397 struct TestStruct { one: i32, two: Empty }
398 
399 fn test_fn() {
400     let s = TestStruct { one: 0, two: Empty };
401 }
402 "#,
403         );
404         check_fix(
405             r#"
406 enum Empty { Foo };
407 
408 struct TestStruct { one: i32, two: Empty }
409 
410 fn test_fn() {
411     let s = TestStruct {$0};
412 }
413 "#,
414             r#"
415 enum Empty { Foo };
416 
417 struct TestStruct { one: i32, two: Empty }
418 
419 fn test_fn() {
420     let s = TestStruct { one: 0, two: Empty::Foo };
421 }
422 "#,
423         );
424 
425         // make sure the assist doesn't fill non Unit variants
426         check_fix(
427             r#"
428 struct Empty {};
429 
430 struct TestStruct { one: i32, two: Empty }
431 
432 fn test_fn() {
433     let s = TestStruct {$0};
434 }
435 "#,
436             r#"
437 struct Empty {};
438 
439 struct TestStruct { one: i32, two: Empty }
440 
441 fn test_fn() {
442     let s = TestStruct { one: 0, two: todo!() };
443 }
444 "#,
445         );
446         check_fix(
447             r#"
448 enum Empty { Foo {} };
449 
450 struct TestStruct { one: i32, two: Empty }
451 
452 fn test_fn() {
453     let s = TestStruct {$0};
454 }
455 "#,
456             r#"
457 enum Empty { Foo {} };
458 
459 struct TestStruct { one: i32, two: Empty }
460 
461 fn test_fn() {
462     let s = TestStruct { one: 0, two: todo!() };
463 }
464 "#,
465         );
466     }
467 
468     #[test]
test_fill_struct_fields_self()469     fn test_fill_struct_fields_self() {
470         check_fix(
471             r#"
472 struct TestStruct { one: i32 }
473 
474 impl TestStruct {
475     fn test_fn() { let s = Self {$0}; }
476 }
477 "#,
478             r#"
479 struct TestStruct { one: i32 }
480 
481 impl TestStruct {
482     fn test_fn() { let s = Self { one: 0 }; }
483 }
484 "#,
485         );
486     }
487 
488     #[test]
test_fill_struct_fields_enum()489     fn test_fill_struct_fields_enum() {
490         check_fix(
491             r#"
492 enum Expr {
493     Bin { lhs: Box<Expr>, rhs: Box<Expr> }
494 }
495 
496 impl Expr {
497     fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
498         Expr::Bin {$0 }
499     }
500 }
501 "#,
502             r#"
503 enum Expr {
504     Bin { lhs: Box<Expr>, rhs: Box<Expr> }
505 }
506 
507 impl Expr {
508     fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
509         Expr::Bin { lhs, rhs }
510     }
511 }
512 "#,
513         );
514     }
515 
516     #[test]
test_fill_struct_fields_partial()517     fn test_fill_struct_fields_partial() {
518         check_fix(
519             r#"
520 struct TestStruct { one: i32, two: i64 }
521 
522 fn test_fn() {
523     let s = TestStruct{ two: 2$0 };
524 }
525 "#,
526             r"
527 struct TestStruct { one: i32, two: i64 }
528 
529 fn test_fn() {
530     let s = TestStruct{ two: 2, one: 0 };
531 }
532 ",
533         );
534     }
535 
536     #[test]
test_fill_struct_fields_new()537     fn test_fill_struct_fields_new() {
538         check_fix(
539             r#"
540 struct TestWithNew(usize);
541 impl TestWithNew {
542     pub fn new() -> Self {
543         Self(0)
544     }
545 }
546 struct TestStruct { one: i32, two: TestWithNew }
547 
548 fn test_fn() {
549     let s = TestStruct{ $0 };
550 }
551 "#,
552             r"
553 struct TestWithNew(usize);
554 impl TestWithNew {
555     pub fn new() -> Self {
556         Self(0)
557     }
558 }
559 struct TestStruct { one: i32, two: TestWithNew }
560 
561 fn test_fn() {
562     let s = TestStruct{ one: 0, two: TestWithNew::new()  };
563 }
564 ",
565         );
566     }
567 
568     #[test]
test_fill_struct_fields_default()569     fn test_fill_struct_fields_default() {
570         check_fix(
571             r#"
572 //- minicore: default, option
573 struct TestWithDefault(usize);
574 impl Default for TestWithDefault {
575     pub fn default() -> Self {
576         Self(0)
577     }
578 }
579 struct TestStruct { one: i32, two: TestWithDefault }
580 
581 fn test_fn() {
582     let s = TestStruct{ $0 };
583 }
584 "#,
585             r"
586 struct TestWithDefault(usize);
587 impl Default for TestWithDefault {
588     pub fn default() -> Self {
589         Self(0)
590     }
591 }
592 struct TestStruct { one: i32, two: TestWithDefault }
593 
594 fn test_fn() {
595     let s = TestStruct{ one: 0, two: TestWithDefault::default()  };
596 }
597 ",
598         );
599     }
600 
601     #[test]
test_fill_struct_fields_raw_ident()602     fn test_fill_struct_fields_raw_ident() {
603         check_fix(
604             r#"
605 struct TestStruct { r#type: u8 }
606 
607 fn test_fn() {
608     TestStruct { $0 };
609 }
610 "#,
611             r"
612 struct TestStruct { r#type: u8 }
613 
614 fn test_fn() {
615     TestStruct { r#type: 0  };
616 }
617 ",
618         );
619     }
620 
621     #[test]
test_fill_struct_fields_no_diagnostic()622     fn test_fill_struct_fields_no_diagnostic() {
623         check_diagnostics(
624             r#"
625 struct TestStruct { one: i32, two: i64 }
626 
627 fn test_fn() {
628     let one = 1;
629     let s = TestStruct{ one, two: 2 };
630 }
631         "#,
632         );
633     }
634 
635     #[test]
test_fill_struct_fields_no_diagnostic_on_spread()636     fn test_fill_struct_fields_no_diagnostic_on_spread() {
637         check_diagnostics(
638             r#"
639 struct TestStruct { one: i32, two: i64 }
640 
641 fn test_fn() {
642     let one = 1;
643     let s = TestStruct{ ..a };
644 }
645 "#,
646         );
647     }
648 
649     #[test]
test_fill_struct_fields_blank_line()650     fn test_fill_struct_fields_blank_line() {
651         check_fix(
652             r#"
653 struct S { a: (), b: () }
654 
655 fn f() {
656     S {
657         $0
658     };
659 }
660 "#,
661             r#"
662 struct S { a: (), b: () }
663 
664 fn f() {
665     S {
666         a: todo!(),
667         b: todo!(),
668     };
669 }
670 "#,
671         );
672     }
673 
674     #[test]
test_fill_struct_fields_shorthand()675     fn test_fill_struct_fields_shorthand() {
676         cov_mark::check!(field_shorthand);
677         check_fix(
678             r#"
679 struct S { a: &'static str, b: i32 }
680 
681 fn f() {
682     let a = "hello";
683     let b = 1i32;
684     S {
685         $0
686     };
687 }
688 "#,
689             r#"
690 struct S { a: &'static str, b: i32 }
691 
692 fn f() {
693     let a = "hello";
694     let b = 1i32;
695     S {
696         a,
697         b,
698     };
699 }
700 "#,
701         );
702     }
703 
704     #[test]
test_fill_struct_fields_shorthand_ty_mismatch()705     fn test_fill_struct_fields_shorthand_ty_mismatch() {
706         check_fix(
707             r#"
708 struct S { a: &'static str, b: i32 }
709 
710 fn f() {
711     let a = "hello";
712     let b = 1usize;
713     S {
714         $0
715     };
716 }
717 "#,
718             r#"
719 struct S { a: &'static str, b: i32 }
720 
721 fn f() {
722     let a = "hello";
723     let b = 1usize;
724     S {
725         a,
726         b: 0,
727     };
728 }
729 "#,
730         );
731     }
732 
733     #[test]
test_fill_struct_fields_shorthand_unifies()734     fn test_fill_struct_fields_shorthand_unifies() {
735         check_fix(
736             r#"
737 struct S<T> { a: &'static str, b: T }
738 
739 fn f() {
740     let a = "hello";
741     let b = 1i32;
742     S {
743         $0
744     };
745 }
746 "#,
747             r#"
748 struct S<T> { a: &'static str, b: T }
749 
750 fn f() {
751     let a = "hello";
752     let b = 1i32;
753     S {
754         a,
755         b,
756     };
757 }
758 "#,
759         );
760     }
761 
762     #[test]
test_fill_struct_pat_fields()763     fn test_fill_struct_pat_fields() {
764         check_fix(
765             r#"
766 struct S { a: &'static str, b: i32 }
767 
768 fn f() {
769     let S {
770         $0
771     };
772 }
773 "#,
774             r#"
775 struct S { a: &'static str, b: i32 }
776 
777 fn f() {
778     let S {
779         a,
780         b,
781     };
782 }
783 "#,
784         );
785     }
786 
787     #[test]
test_fill_struct_pat_fields_partial()788     fn test_fill_struct_pat_fields_partial() {
789         check_fix(
790             r#"
791 struct S { a: &'static str, b: i32 }
792 
793 fn f() {
794     let S {
795         a,$0
796     };
797 }
798 "#,
799             r#"
800 struct S { a: &'static str, b: i32 }
801 
802 fn f() {
803     let S {
804         a,
805         b,
806     };
807 }
808 "#,
809         );
810     }
811 
812     #[test]
import_extern_crate_clash_with_inner_item()813     fn import_extern_crate_clash_with_inner_item() {
814         // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
815 
816         check_diagnostics(
817             r#"
818 //- /lib.rs crate:lib deps:jwt
819 mod permissions;
820 
821 use permissions::jwt;
822 
823 fn f() {
824     fn inner() {}
825     jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
826 }
827 
828 //- /permissions.rs
829 pub mod jwt  {
830     pub struct Claims {}
831 }
832 
833 //- /jwt/lib.rs crate:jwt
834 pub struct Claims {
835     field: u8,
836 }
837         "#,
838         );
839     }
840 }
841