ide_diagnostics/handlers/
no_such_field.rs1use 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
15pub(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 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 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}