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