ide_diagnostics/handlers/
missing_fields.rs

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
25// Diagnostic: missing-fields
26//
27// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
28//
29// Example:
30//
31// ```rust
32// struct A { a: u8, b: u8 }
33//
34// let a = A { a: 10 };
35// ```
36pub(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    // Note that although we could add a diagnostics to
56    // fill the missing tuple field, e.g :
57    // `struct A(usize);`
58    // `let a = A { 0: () }`
59    // but it is uncommon usage and it should not be encouraged.
60    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                // we can't map the diff up into the macro input unfortunately, as the macro loses all
80                // whitespace information so the diff wouldn't be applicable no matter what
81                // This has the downside that the cursor will be moved in macros by doing it without a diff
82                // but that is a trade off we can make.
83                // FIXME: this also currently discards a lot of whitespace in the input... we really need a formatter here
84                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    // Look for a ::new() associated function
228    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        // make sure the assist doesn't fill non Unit variants
445        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        // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
835
836        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}