Skip to main content

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::Variant::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::Variant::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::Variant::EnumVariant(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 (indent, offset, postfix, needs_comma) =
101        if let Some(last_field) = record_fields.fields().last() {
102            let indent = IndentLevel::from_node(last_field.syntax());
103            let offset = last_field.syntax().text_range().end();
104            let needs_comma = !last_field.to_string().ends_with(',');
105            (indent, offset, String::new(), needs_comma)
106        } else {
107            let indent = IndentLevel::from_node(record_fields.syntax());
108            let offset = record_fields.l_curly_token()?.text_range().end();
109            let postfix = if record_fields.syntax().text().contains_char('\n') {
110                ",".into()
111            } else {
112                format!(",\n{indent}")
113            };
114            (indent + 1, offset, postfix, false)
115        };
116
117    let mut new_field = new_field.to_string();
118    // FIXME: check submodule instead of FileId
119    let vis = if usage_file_id != def_file_id && !matches!(def_id, hir::Variant::EnumVariant(_)) {
120        "pub(crate) "
121    } else {
122        ""
123    };
124    let comma = if needs_comma { "," } else { "" };
125    new_field = format!("{comma}\n{indent}{vis}{new_field}{postfix}");
126
127    let source_change = SourceChange::from_text_edit(
128        def_file_id.file_id(sema.db),
129        TextEdit::insert(offset, new_field),
130    );
131
132    return Some(vec![fix(
133        "create_field",
134        "Create field",
135        source_change,
136        sema.original_range(record_expr_field.syntax()).range,
137    )]);
138
139    fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
140        match field_def_list {
141            ast::FieldList::RecordFieldList(it) => Some(it),
142            ast::FieldList::TupleFieldList(_) => None,
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use crate::tests::{check_diagnostics, check_fix, check_no_fix};
150
151    #[test]
152    fn dont_work_for_field_with_disabled_cfg() {
153        check_diagnostics(
154            r#"
155struct Test {
156    #[cfg(feature = "hello")]
157    test: u32,
158    other: u32
159}
160
161fn main() {
162    let a = Test {
163        #[cfg(feature = "hello")]
164        test: 1,
165        other: 1
166    };
167
168    let Test {
169        #[cfg(feature = "hello")]
170        test,
171        mut other,
172        ..
173    } = a;
174
175    other += 1;
176}
177"#,
178        );
179    }
180
181    #[test]
182    fn no_such_field_diagnostics() {
183        check_diagnostics(
184            r#"
185struct S { foo: i32, bar: () }
186impl S {
187    fn new(
188        s@S {
189        //^ 💡 error: missing structure fields:
190        //|    - bar
191            foo,
192            baz: baz2,
193          //^^^^^^^^^ error: no such field
194            qux
195          //^^^ error: no such field
196        }: S
197    ) -> S {
198        S {
199      //^ 💡 error: missing structure fields:
200      //|    - bar
201            foo,
202            baz: baz2,
203          //^^^^^^^^^ error: no such field
204            qux
205          //^^^ error: no such field
206        } = s;
207        S {
208      //^ 💡 error: missing structure fields:
209      //|    - bar
210            foo: 92,
211            baz: 62,
212          //^^^^^^^ 💡 error: no such field
213            qux
214          //^^^ error: no such field
215        }
216    }
217}
218"#,
219        );
220    }
221    #[test]
222    fn no_such_field_with_feature_flag_diagnostics() {
223        check_diagnostics(
224            r#"
225//- /lib.rs crate:foo cfg:feature=foo
226struct MyStruct {
227    my_val: usize,
228    #[cfg(feature = "foo")]
229    bar: bool,
230}
231
232impl MyStruct {
233    #[cfg(feature = "foo")]
234    pub(crate) fn new(my_val: usize, bar: bool) -> Self {
235        Self { my_val, bar }
236    }
237    #[cfg(not(feature = "foo"))]
238    pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
239        Self { my_val }
240    }
241}
242"#,
243        );
244    }
245
246    #[test]
247    fn no_such_field_enum_with_feature_flag_diagnostics() {
248        check_diagnostics(
249            r#"
250//- /lib.rs crate:foo cfg:feature=foo
251enum Foo {
252    #[cfg(not(feature = "foo"))]
253    Buz,
254    #[cfg(feature = "foo")]
255    Bar,
256    Baz
257}
258
259fn test_fn(f: Foo) {
260    match f {
261        Foo::Bar => {},
262        Foo::Baz => {},
263    }
264}
265"#,
266        );
267    }
268
269    #[test]
270    fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
271        check_diagnostics(
272            r#"
273//- /lib.rs crate:foo cfg:feature=foo
274struct S {
275    #[cfg(feature = "foo")]
276    foo: u32,
277    #[cfg(not(feature = "foo"))]
278    bar: u32,
279}
280
281impl S {
282    #[cfg(feature = "foo")]
283    fn new(foo: u32) -> Self {
284        Self { foo }
285    }
286    #[cfg(not(feature = "foo"))]
287    fn new(bar: u32) -> Self {
288        Self { bar }
289    }
290    fn new2(bar: u32) -> Self {
291        #[cfg(feature = "foo")]
292        { Self { foo: bar } }
293        #[cfg(not(feature = "foo"))]
294        { Self { bar } }
295    }
296    fn new2(val: u32) -> Self {
297        Self {
298            #[cfg(feature = "foo")]
299            foo: val,
300            #[cfg(not(feature = "foo"))]
301            bar: val,
302        }
303    }
304}
305"#,
306        );
307    }
308
309    #[test]
310    fn no_such_field_with_type_macro() {
311        check_diagnostics(
312            r#"
313macro_rules! Type { () => { u32 }; }
314struct Foo { bar: Type![] }
315
316impl Foo {
317    fn new() -> Self {
318        Foo { bar: 0 }
319    }
320}
321"#,
322        );
323    }
324
325    #[test]
326    fn test_add_field_from_usage() {
327        check_fix(
328            r"
329fn main() {
330    Foo { bar: 3, baz$0: false};
331}
332struct Foo {
333    bar: i32
334}
335",
336            r"
337fn main() {
338    Foo { bar: 3, baz: false};
339}
340struct Foo {
341    bar: i32,
342    baz: bool
343}
344",
345        )
346    }
347
348    #[test]
349    fn test_add_field_from_usage_with_empty_struct() {
350        check_fix(
351            r"
352fn main() {
353    Foo { bar$0: false };
354}
355struct Foo {}
356",
357            r"
358fn main() {
359    Foo { bar: false };
360}
361struct Foo {
362    bar: bool,
363}
364",
365        );
366
367        check_fix(
368            r"
369fn main() {
370    Foo { bar$0: false };
371}
372struct Foo {
373}
374",
375            r"
376fn main() {
377    Foo { bar: false };
378}
379struct Foo {
380    bar: bool,
381}
382",
383        );
384    }
385
386    #[test]
387    fn test_add_field_in_other_file_from_usage() {
388        check_fix(
389            r#"
390//- /main.rs
391mod foo;
392
393fn main() {
394    foo::Foo { bar: 3, $0baz: false};
395}
396//- /foo.rs
397pub struct Foo {
398    bar: i32
399}
400"#,
401            r#"
402pub struct Foo {
403    bar: i32,
404    pub(crate) baz: bool
405}
406"#,
407        )
408    }
409
410    #[test]
411    fn test_add_enum_variant_field_in_other_file_from_usage() {
412        check_fix(
413            r#"
414//- /main.rs
415mod foo;
416
417fn main() {
418    foo::Foo::Variant { bar: 3, $0baz: false};
419}
420//- /foo.rs
421pub enum Foo {
422    Variant {
423        bar: i32
424    }
425}
426"#,
427            r#"
428pub enum Foo {
429    Variant {
430        bar: i32,
431        baz: bool
432    }
433}
434"#,
435        )
436    }
437
438    #[test]
439    fn test_tuple_field_on_record_struct() {
440        check_no_fix(
441            r#"
442struct Struct {}
443fn main() {
444    Struct {
445        0$0: 0
446    }
447}
448"#,
449        )
450    }
451
452    #[test]
453    fn test_struct_field_private() {
454        check_diagnostics(
455            r#"
456mod m {
457    pub struct Struct {
458        field: u32,
459        field2: u32,
460    }
461}
462fn f(s@m::Struct {
463    field: f,
464  //^^^^^^^^ error: field is private
465    field2
466  //^^^^^^ error: field is private
467}: m::Struct) {
468    // assignee expression
469    m::Struct {
470        field: 0,
471      //^^^^^^^^ 💡 error: field is private
472        field2
473      //^^^^^^ 💡 error: field is private
474    } = s;
475    m::Struct {
476        field: 0,
477      //^^^^^^^^ 💡 error: field is private
478        field2
479      //^^^^^^ 💡 error: field is private
480    };
481}
482"#,
483        )
484    }
485
486    #[test]
487    fn test_struct_field_private_same_crate_fix() {
488        check_diagnostics(
489            r#"
490mod m {
491    pub struct Struct {
492        field: u32,
493    }
494}
495fn f() {
496    let _ = m::Struct {
497        field: 0,
498      //^^^^^^^^ 💡 error: field is private
499    };
500}
501"#,
502        );
503
504        check_fix(
505            r#"
506mod m {
507    pub struct Struct {
508        field: u32,
509    }
510}
511fn f() {
512    let _ = m::Struct {
513        field$0: 0,
514    };
515}
516"#,
517            r#"
518mod m {
519    pub struct Struct {
520        pub(crate) field: u32,
521    }
522}
523fn f() {
524    let _ = m::Struct {
525        field: 0,
526    };
527}
528"#,
529        );
530    }
531
532    #[test]
533    fn test_struct_field_private_other_crate_fix() {
534        check_fix(
535            r#"
536//- /lib.rs crate:another_crate
537pub struct Struct {
538    field: u32,
539}
540//- /lib.rs crate:this_crate deps:another_crate
541use another_crate;
542
543fn f() {
544    let _ = another_crate::Struct {
545        field$0: 0,
546    };
547}
548"#,
549            r#"
550pub struct Struct {
551    pub field: u32,
552}
553"#,
554        );
555    }
556
557    #[test]
558    fn editions_between_macros() {
559        check_diagnostics(
560            r#"
561//- /edition2015.rs crate:edition2015 edition:2015
562#[macro_export]
563macro_rules! pass_expr_thorough {
564    ($e:expr) => { $e };
565}
566
567//- /edition2018.rs crate:edition2018 deps:edition2015 edition:2018
568async fn bar() {}
569async fn foo() {
570    edition2015::pass_expr_thorough!(bar().await);
571}
572        "#,
573        );
574        check_diagnostics(
575            r#"
576//- /edition2018.rs crate:edition2018 edition:2018
577pub async fn bar() {}
578#[macro_export]
579macro_rules! make_await {
580    () => { async { $crate::bar().await }; };
581}
582
583//- /edition2015.rs crate:edition2015 deps:edition2018 edition:2015
584fn foo() {
585    edition2018::make_await!();
586}
587        "#,
588        );
589    }
590}