ide_diagnostics/handlers/
no_such_field.rs

1use either::Either;
2use hir::{HasSource, HirDisplay, Semantics, VariantId, db::ExpandDatabase};
3use ide_db::text_edit::TextEdit;
4use ide_db::{EditionedFileId, RootDatabase, source_change::SourceChange};
5use syntax::{
6    AstNode,
7    ast::{self, edit::IndentLevel, make},
8};
9
10use crate::{
11    Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, fix,
12    handlers::private_field::field_is_private_fixes,
13};
14
15// Diagnostic: no-such-field
16//
17// This diagnostic is triggered if created structure does not have field provided in record.
18pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
19    let (code, message) = if d.private.is_some() {
20        ("E0451", "field is private")
21    } else if let VariantId::EnumVariantId(_) = d.variant {
22        ("E0559", "no such field")
23    } else {
24        ("E0560", "no such field")
25    };
26
27    let node = d.field.map(Into::into);
28    Diagnostic::new_with_syntax_node_ptr(ctx, DiagnosticCode::RustcHardError(code), message, node)
29        .stable()
30        .with_fixes(fixes(ctx, d))
31}
32
33fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
34    // FIXME: quickfix for pattern
35    let root = ctx.sema.db.parse_or_expand(d.field.file_id);
36    match &d.field.value.to_node(&root) {
37        Either::Left(node) => {
38            if let Some(private_field) = d.private {
39                field_is_private_fixes(
40                    &ctx.sema,
41                    d.field.file_id.original_file(ctx.sema.db),
42                    private_field,
43                    ctx.sema.original_range(node.syntax()).range,
44                )
45            } else {
46                missing_record_expr_field_fixes(
47                    &ctx.sema,
48                    d.field.file_id.original_file(ctx.sema.db),
49                    node,
50                )
51            }
52        }
53        _ => None,
54    }
55}
56
57fn missing_record_expr_field_fixes(
58    sema: &Semantics<'_, RootDatabase>,
59    usage_file_id: EditionedFileId,
60    record_expr_field: &ast::RecordExprField,
61) -> Option<Vec<Assist>> {
62    let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
63    let def_id = sema.resolve_variant(record_lit)?;
64    let module;
65    let def_file_id;
66    let record_fields = match def_id {
67        hir::VariantDef::Struct(s) => {
68            module = s.module(sema.db);
69            let source = s.source(sema.db)?;
70            def_file_id = source.file_id;
71            let fields = source.value.field_list()?;
72            record_field_list(fields)?
73        }
74        hir::VariantDef::Union(u) => {
75            module = u.module(sema.db);
76            let source = u.source(sema.db)?;
77            def_file_id = source.file_id;
78            source.value.record_field_list()?
79        }
80        hir::VariantDef::Variant(e) => {
81            module = e.module(sema.db);
82            let source = e.source(sema.db)?;
83            def_file_id = source.file_id;
84            let fields = source.value.field_list()?;
85            record_field_list(fields)?
86        }
87    };
88    let def_file_id = def_file_id.original_file(sema.db);
89
90    let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?.adjusted();
91    if new_field_type.is_unknown() {
92        return None;
93    }
94    let new_field = make::record_field(
95        None,
96        make::name(record_expr_field.field_name()?.ident_token()?.text()),
97        make::ty(&new_field_type.display_source_code(sema.db, module.into(), true).ok()?),
98    );
99
100    let last_field = record_fields.fields().last()?;
101    let last_field_syntax = last_field.syntax();
102    let indent = IndentLevel::from_node(last_field_syntax);
103
104    let mut new_field = new_field.to_string();
105    // FIXME: check submodule instead of FileId
106    if usage_file_id != def_file_id && !matches!(def_id, hir::VariantDef::Variant(_)) {
107        new_field = format!("pub(crate) {new_field}");
108    }
109    new_field = format!("\n{indent}{new_field}");
110
111    let needs_comma = !last_field_syntax.to_string().ends_with(',');
112    if needs_comma {
113        new_field = format!(",{new_field}");
114    }
115
116    let source_change = SourceChange::from_text_edit(
117        def_file_id.file_id(sema.db),
118        TextEdit::insert(last_field_syntax.text_range().end(), new_field),
119    );
120
121    return Some(vec![fix(
122        "create_field",
123        "Create field",
124        source_change,
125        sema.original_range(record_expr_field.syntax()).range,
126    )]);
127
128    fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
129        match field_def_list {
130            ast::FieldList::RecordFieldList(it) => Some(it),
131            ast::FieldList::TupleFieldList(_) => None,
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use crate::tests::{check_diagnostics, check_fix, check_no_fix};
139
140    #[test]
141    fn dont_work_for_field_with_disabled_cfg() {
142        check_diagnostics(
143            r#"
144struct Test {
145    #[cfg(feature = "hello")]
146    test: u32,
147    other: u32
148}
149
150fn main() {
151    let a = Test {
152        #[cfg(feature = "hello")]
153        test: 1,
154        other: 1
155    };
156
157    let Test {
158        #[cfg(feature = "hello")]
159        test,
160        mut other,
161        ..
162    } = a;
163
164    other += 1;
165}
166"#,
167        );
168    }
169
170    #[test]
171    fn no_such_field_diagnostics() {
172        check_diagnostics(
173            r#"
174struct S { foo: i32, bar: () }
175impl S {
176    fn new(
177        s@S {
178        //^ 💡 error: missing structure fields:
179        //|    - bar
180            foo,
181            baz: baz2,
182          //^^^^^^^^^ error: no such field
183            qux
184          //^^^ error: no such field
185        }: S
186    ) -> S {
187        S {
188      //^ 💡 error: missing structure fields:
189      //|    - bar
190            foo,
191            baz: baz2,
192          //^^^^^^^^^ error: no such field
193            qux
194          //^^^ error: no such field
195        } = s;
196        S {
197      //^ 💡 error: missing structure fields:
198      //|    - bar
199            foo: 92,
200            baz: 62,
201          //^^^^^^^ 💡 error: no such field
202            qux
203          //^^^ error: no such field
204        }
205    }
206}
207"#,
208        );
209    }
210    #[test]
211    fn no_such_field_with_feature_flag_diagnostics() {
212        check_diagnostics(
213            r#"
214//- /lib.rs crate:foo cfg:feature=foo
215struct MyStruct {
216    my_val: usize,
217    #[cfg(feature = "foo")]
218    bar: bool,
219}
220
221impl MyStruct {
222    #[cfg(feature = "foo")]
223    pub(crate) fn new(my_val: usize, bar: bool) -> Self {
224        Self { my_val, bar }
225    }
226    #[cfg(not(feature = "foo"))]
227    pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
228        Self { my_val }
229    }
230}
231"#,
232        );
233    }
234
235    #[test]
236    fn no_such_field_enum_with_feature_flag_diagnostics() {
237        check_diagnostics(
238            r#"
239//- /lib.rs crate:foo cfg:feature=foo
240enum Foo {
241    #[cfg(not(feature = "foo"))]
242    Buz,
243    #[cfg(feature = "foo")]
244    Bar,
245    Baz
246}
247
248fn test_fn(f: Foo) {
249    match f {
250        Foo::Bar => {},
251        Foo::Baz => {},
252    }
253}
254"#,
255        );
256    }
257
258    #[test]
259    fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
260        check_diagnostics(
261            r#"
262//- /lib.rs crate:foo cfg:feature=foo
263struct S {
264    #[cfg(feature = "foo")]
265    foo: u32,
266    #[cfg(not(feature = "foo"))]
267    bar: u32,
268}
269
270impl S {
271    #[cfg(feature = "foo")]
272    fn new(foo: u32) -> Self {
273        Self { foo }
274    }
275    #[cfg(not(feature = "foo"))]
276    fn new(bar: u32) -> Self {
277        Self { bar }
278    }
279    fn new2(bar: u32) -> Self {
280        #[cfg(feature = "foo")]
281        { Self { foo: bar } }
282        #[cfg(not(feature = "foo"))]
283        { Self { bar } }
284    }
285    fn new2(val: u32) -> Self {
286        Self {
287            #[cfg(feature = "foo")]
288            foo: val,
289            #[cfg(not(feature = "foo"))]
290            bar: val,
291        }
292    }
293}
294"#,
295        );
296    }
297
298    #[test]
299    fn no_such_field_with_type_macro() {
300        check_diagnostics(
301            r#"
302macro_rules! Type { () => { u32 }; }
303struct Foo { bar: Type![] }
304
305impl Foo {
306    fn new() -> Self {
307        Foo { bar: 0 }
308    }
309}
310"#,
311        );
312    }
313
314    #[test]
315    fn test_add_field_from_usage() {
316        check_fix(
317            r"
318fn main() {
319    Foo { bar: 3, baz$0: false};
320}
321struct Foo {
322    bar: i32
323}
324",
325            r"
326fn main() {
327    Foo { bar: 3, baz: false};
328}
329struct Foo {
330    bar: i32,
331    baz: bool
332}
333",
334        )
335    }
336
337    #[test]
338    fn test_add_field_in_other_file_from_usage() {
339        check_fix(
340            r#"
341//- /main.rs
342mod foo;
343
344fn main() {
345    foo::Foo { bar: 3, $0baz: false};
346}
347//- /foo.rs
348pub struct Foo {
349    bar: i32
350}
351"#,
352            r#"
353pub struct Foo {
354    bar: i32,
355    pub(crate) baz: bool
356}
357"#,
358        )
359    }
360
361    #[test]
362    fn test_add_enum_variant_field_in_other_file_from_usage() {
363        check_fix(
364            r#"
365//- /main.rs
366mod foo;
367
368fn main() {
369    foo::Foo::Variant { bar: 3, $0baz: false};
370}
371//- /foo.rs
372pub enum Foo {
373    Variant {
374        bar: i32
375    }
376}
377"#,
378            r#"
379pub enum Foo {
380    Variant {
381        bar: i32,
382        baz: bool
383    }
384}
385"#,
386        )
387    }
388
389    #[test]
390    fn test_tuple_field_on_record_struct() {
391        check_no_fix(
392            r#"
393struct Struct {}
394fn main() {
395    Struct {
396        0$0: 0
397    }
398}
399"#,
400        )
401    }
402
403    #[test]
404    fn test_struct_field_private() {
405        check_diagnostics(
406            r#"
407mod m {
408    pub struct Struct {
409        field: u32,
410        field2: u32,
411    }
412}
413fn f(s@m::Struct {
414    field: f,
415  //^^^^^^^^ error: field is private
416    field2
417  //^^^^^^ error: field is private
418}: m::Struct) {
419    // assignee expression
420    m::Struct {
421        field: 0,
422      //^^^^^^^^ 💡 error: field is private
423        field2
424      //^^^^^^ 💡 error: field is private
425    } = s;
426    m::Struct {
427        field: 0,
428      //^^^^^^^^ 💡 error: field is private
429        field2
430      //^^^^^^ 💡 error: field is private
431    };
432}
433"#,
434        )
435    }
436
437    #[test]
438    fn test_struct_field_private_same_crate_fix() {
439        check_diagnostics(
440            r#"
441mod m {
442    pub struct Struct {
443        field: u32,
444    }
445}
446fn f() {
447    let _ = m::Struct {
448        field: 0,
449      //^^^^^^^^ 💡 error: field is private
450    };
451}
452"#,
453        );
454
455        check_fix(
456            r#"
457mod m {
458    pub struct Struct {
459        field: u32,
460    }
461}
462fn f() {
463    let _ = m::Struct {
464        field$0: 0,
465    };
466}
467"#,
468            r#"
469mod m {
470    pub struct Struct {
471        pub(crate) field: u32,
472    }
473}
474fn f() {
475    let _ = m::Struct {
476        field: 0,
477    };
478}
479"#,
480        );
481    }
482
483    #[test]
484    fn test_struct_field_private_other_crate_fix() {
485        check_fix(
486            r#"
487//- /lib.rs crate:another_crate
488pub struct Struct {
489    field: u32,
490}
491//- /lib.rs crate:this_crate deps:another_crate
492use another_crate;
493
494fn f() {
495    let _ = another_crate::Struct {
496        field$0: 0,
497    };
498}
499"#,
500            r#"
501pub struct Struct {
502    pub field: u32,
503}
504"#,
505        );
506    }
507
508    #[test]
509    fn editions_between_macros() {
510        check_diagnostics(
511            r#"
512//- /edition2015.rs crate:edition2015 edition:2015
513#[macro_export]
514macro_rules! pass_expr_thorough {
515    ($e:expr) => { $e };
516}
517
518//- /edition2018.rs crate:edition2018 deps:edition2015 edition:2018
519async fn bar() {}
520async fn foo() {
521    edition2015::pass_expr_thorough!(bar().await);
522}
523        "#,
524        );
525        check_diagnostics(
526            r#"
527//- /edition2018.rs crate:edition2018 edition:2018
528pub async fn bar() {}
529#[macro_export]
530macro_rules! make_await {
531    () => { async { $crate::bar().await }; };
532}
533
534//- /edition2015.rs crate:edition2015 deps:edition2018 edition:2015
535fn foo() {
536    edition2018::make_await!();
537}
538        "#,
539        );
540    }
541}