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::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 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}