1 use hir::{db::AstDatabase, HasSource, HirDisplay, Semantics};
2 use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3 use syntax::{
4     ast::{self, edit::IndentLevel, make},
5     AstNode,
6 };
7 use text_edit::TextEdit;
8 
9 use crate::{fix, Assist, Diagnostic, DiagnosticsContext};
10 
11 // Diagnostic: no-such-field
12 //
13 // This diagnostic is triggered if created structure does not have field provided in record.
no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic14 pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
15     Diagnostic::new(
16         "no-such-field",
17         "no such field",
18         ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range,
19     )
20     .with_fixes(fixes(ctx, d))
21 }
22 
fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>>23 fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
24     let root = ctx.sema.db.parse_or_expand(d.field.file_id)?;
25     missing_record_expr_field_fixes(
26         &ctx.sema,
27         d.field.file_id.original_file(ctx.sema.db),
28         &d.field.value.to_node(&root),
29     )
30 }
31 
missing_record_expr_field_fixes( sema: &Semantics<RootDatabase>, usage_file_id: FileId, record_expr_field: &ast::RecordExprField, ) -> Option<Vec<Assist>>32 fn missing_record_expr_field_fixes(
33     sema: &Semantics<RootDatabase>,
34     usage_file_id: FileId,
35     record_expr_field: &ast::RecordExprField,
36 ) -> Option<Vec<Assist>> {
37     let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
38     let def_id = sema.resolve_variant(record_lit)?;
39     let module;
40     let def_file_id;
41     let record_fields = match def_id {
42         hir::VariantDef::Struct(s) => {
43             module = s.module(sema.db);
44             let source = s.source(sema.db)?;
45             def_file_id = source.file_id;
46             let fields = source.value.field_list()?;
47             record_field_list(fields)?
48         }
49         hir::VariantDef::Union(u) => {
50             module = u.module(sema.db);
51             let source = u.source(sema.db)?;
52             def_file_id = source.file_id;
53             source.value.record_field_list()?
54         }
55         hir::VariantDef::Variant(e) => {
56             module = e.module(sema.db);
57             let source = e.source(sema.db)?;
58             def_file_id = source.file_id;
59             let fields = source.value.field_list()?;
60             record_field_list(fields)?
61         }
62     };
63     let def_file_id = def_file_id.original_file(sema.db);
64 
65     let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?.adjusted();
66     if new_field_type.is_unknown() {
67         return None;
68     }
69     let new_field = make::record_field(
70         None,
71         make::name(&record_expr_field.field_name()?.text()),
72         make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
73     );
74 
75     let last_field = record_fields.fields().last()?;
76     let last_field_syntax = last_field.syntax();
77     let indent = IndentLevel::from_node(last_field_syntax);
78 
79     let mut new_field = new_field.to_string();
80     if usage_file_id != def_file_id {
81         new_field = format!("pub(crate) {}", new_field);
82     }
83     new_field = format!("\n{}{}", indent, new_field);
84 
85     let needs_comma = !last_field_syntax.to_string().ends_with(',');
86     if needs_comma {
87         new_field = format!(",{}", new_field);
88     }
89 
90     let source_change = SourceChange::from_text_edit(
91         def_file_id,
92         TextEdit::insert(last_field_syntax.text_range().end(), new_field),
93     );
94 
95     return Some(vec![fix(
96         "create_field",
97         "Create field",
98         source_change,
99         record_expr_field.syntax().text_range(),
100     )]);
101 
102     fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
103         match field_def_list {
104             ast::FieldList::RecordFieldList(it) => Some(it),
105             ast::FieldList::TupleFieldList(_) => None,
106         }
107     }
108 }
109 
110 #[cfg(test)]
111 mod tests {
112     use crate::tests::{check_diagnostics, check_fix};
113 
114     #[test]
no_such_field_diagnostics()115     fn no_such_field_diagnostics() {
116         check_diagnostics(
117             r#"
118 struct S { foo: i32, bar: () }
119 impl S {
120     fn new() -> S {
121         S {
122       //^ �� error: missing structure fields:
123       //|    - bar
124             foo: 92,
125             baz: 62,
126           //^^^^^^^ �� error: no such field
127         }
128     }
129 }
130 "#,
131         );
132     }
133     #[test]
no_such_field_with_feature_flag_diagnostics()134     fn no_such_field_with_feature_flag_diagnostics() {
135         check_diagnostics(
136             r#"
137 //- /lib.rs crate:foo cfg:feature=foo
138 struct MyStruct {
139     my_val: usize,
140     #[cfg(feature = "foo")]
141     bar: bool,
142 }
143 
144 impl MyStruct {
145     #[cfg(feature = "foo")]
146     pub(crate) fn new(my_val: usize, bar: bool) -> Self {
147         Self { my_val, bar }
148     }
149     #[cfg(not(feature = "foo"))]
150     pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
151         Self { my_val }
152     }
153 }
154 "#,
155         );
156     }
157 
158     #[test]
no_such_field_enum_with_feature_flag_diagnostics()159     fn no_such_field_enum_with_feature_flag_diagnostics() {
160         check_diagnostics(
161             r#"
162 //- /lib.rs crate:foo cfg:feature=foo
163 enum Foo {
164     #[cfg(not(feature = "foo"))]
165     Buz,
166     #[cfg(feature = "foo")]
167     Bar,
168     Baz
169 }
170 
171 fn test_fn(f: Foo) {
172     match f {
173         Foo::Bar => {},
174         Foo::Baz => {},
175     }
176 }
177 "#,
178         );
179     }
180 
181     #[test]
no_such_field_with_feature_flag_diagnostics_on_struct_lit()182     fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
183         check_diagnostics(
184             r#"
185 //- /lib.rs crate:foo cfg:feature=foo
186 struct S {
187     #[cfg(feature = "foo")]
188     foo: u32,
189     #[cfg(not(feature = "foo"))]
190     bar: u32,
191 }
192 
193 impl S {
194     #[cfg(feature = "foo")]
195     fn new(foo: u32) -> Self {
196         Self { foo }
197     }
198     #[cfg(not(feature = "foo"))]
199     fn new(bar: u32) -> Self {
200         Self { bar }
201     }
202     fn new2(bar: u32) -> Self {
203         #[cfg(feature = "foo")]
204         { Self { foo: bar } }
205         #[cfg(not(feature = "foo"))]
206         { Self { bar } }
207     }
208     fn new2(val: u32) -> Self {
209         Self {
210             #[cfg(feature = "foo")]
211             foo: val,
212             #[cfg(not(feature = "foo"))]
213             bar: val,
214         }
215     }
216 }
217 "#,
218         );
219     }
220 
221     #[test]
no_such_field_with_type_macro()222     fn no_such_field_with_type_macro() {
223         check_diagnostics(
224             r#"
225 macro_rules! Type { () => { u32 }; }
226 struct Foo { bar: Type![] }
227 
228 impl Foo {
229     fn new() -> Self {
230         Foo { bar: 0 }
231     }
232 }
233 "#,
234         );
235     }
236 
237     #[test]
test_add_field_from_usage()238     fn test_add_field_from_usage() {
239         check_fix(
240             r"
241 fn main() {
242     Foo { bar: 3, baz$0: false};
243 }
244 struct Foo {
245     bar: i32
246 }
247 ",
248             r"
249 fn main() {
250     Foo { bar: 3, baz: false};
251 }
252 struct Foo {
253     bar: i32,
254     baz: bool
255 }
256 ",
257         )
258     }
259 
260     #[test]
test_add_field_in_other_file_from_usage()261     fn test_add_field_in_other_file_from_usage() {
262         check_fix(
263             r#"
264 //- /main.rs
265 mod foo;
266 
267 fn main() {
268     foo::Foo { bar: 3, $0baz: false};
269 }
270 //- /foo.rs
271 struct Foo {
272     bar: i32
273 }
274 "#,
275             r#"
276 struct Foo {
277     bar: i32,
278     pub(crate) baz: bool
279 }
280 "#,
281         )
282     }
283 }
284