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