aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_diagnostics/src/handlers/missing_fields.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_diagnostics/src/handlers/missing_fields.rs')
-rw-r--r--crates/ide_diagnostics/src/handlers/missing_fields.rs355
1 files changed, 355 insertions, 0 deletions
diff --git a/crates/ide_diagnostics/src/handlers/missing_fields.rs b/crates/ide_diagnostics/src/handlers/missing_fields.rs
new file mode 100644
index 000000000..bc56e0342
--- /dev/null
+++ b/crates/ide_diagnostics/src/handlers/missing_fields.rs
@@ -0,0 +1,355 @@
1use either::Either;
2use hir::{db::AstDatabase, InFile};
3use ide_db::{assists::Assist, source_change::SourceChange};
4use stdx::format_to;
5use syntax::{algo, ast::make, AstNode, SyntaxNodePtr};
6use text_edit::TextEdit;
7
8use crate::{fix, Diagnostic, DiagnosticsContext};
9
10// Diagnostic: missing-fields
11//
12// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
13//
14// Example:
15//
16// ```rust
17// struct A { a: u8, b: u8 }
18//
19// let a = A { a: 10 };
20// ```
21pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
22 let mut message = String::from("missing structure fields:\n");
23 for field in &d.missed_fields {
24 format_to!(message, "- {}\n", field);
25 }
26
27 let ptr = InFile::new(
28 d.file,
29 d.field_list_parent_path
30 .clone()
31 .map(SyntaxNodePtr::from)
32 .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
33 );
34
35 Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
36 .with_fixes(fixes(ctx, d))
37}
38
39fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
40 // Note that although we could add a diagnostics to
41 // fill the missing tuple field, e.g :
42 // `struct A(usize);`
43 // `let a = A { 0: () }`
44 // but it is uncommon usage and it should not be encouraged.
45 if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
46 return None;
47 }
48
49 let root = ctx.sema.db.parse_or_expand(d.file)?;
50 let field_list_parent = match &d.field_list_parent {
51 Either::Left(record_expr) => record_expr.to_node(&root),
52 // FIXE: patterns should be fixable as well.
53 Either::Right(_) => return None,
54 };
55 let old_field_list = field_list_parent.record_expr_field_list()?;
56 let new_field_list = old_field_list.clone_for_update();
57 for f in d.missed_fields.iter() {
58 let field =
59 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()))
60 .clone_for_update();
61 new_field_list.add_field(field);
62 }
63
64 let edit = {
65 let mut builder = TextEdit::builder();
66 algo::diff(old_field_list.syntax(), new_field_list.syntax()).into_text_edit(&mut builder);
67 builder.finish()
68 };
69 Some(vec![fix(
70 "fill_missing_fields",
71 "Fill struct fields",
72 SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit),
73 ctx.sema.original_range(field_list_parent.syntax()).range,
74 )])
75}
76
77#[cfg(test)]
78mod tests {
79 use crate::tests::{check_diagnostics, check_fix};
80
81 #[test]
82 fn missing_record_pat_field_diagnostic() {
83 check_diagnostics(
84 r#"
85struct S { foo: i32, bar: () }
86fn baz(s: S) {
87 let S { foo: _ } = s;
88 //^ error: missing structure fields:
89 //| - bar
90}
91"#,
92 );
93 }
94
95 #[test]
96 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
97 check_diagnostics(
98 r"
99struct S { foo: i32, bar: () }
100fn baz(s: S) -> i32 {
101 match s {
102 S { foo, .. } => foo,
103 }
104}
105",
106 )
107 }
108
109 #[test]
110 fn missing_record_pat_field_box() {
111 check_diagnostics(
112 r"
113struct S { s: Box<u32> }
114fn x(a: S) {
115 let S { box s } = a;
116}
117",
118 )
119 }
120
121 #[test]
122 fn missing_record_pat_field_ref() {
123 check_diagnostics(
124 r"
125struct S { s: u32 }
126fn x(a: S) {
127 let S { ref s } = a;
128}
129",
130 )
131 }
132
133 #[test]
134 fn range_mapping_out_of_macros() {
135 // FIXME: this is very wrong, but somewhat tricky to fix.
136 check_fix(
137 r#"
138fn some() {}
139fn items() {}
140fn here() {}
141
142macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
143
144fn main() {
145 let _x = id![Foo { a: $042 }];
146}
147
148pub struct Foo { pub a: i32, pub b: i32 }
149"#,
150 r#"
151fn some(, b: () ) {}
152fn items() {}
153fn here() {}
154
155macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
156
157fn main() {
158 let _x = id![Foo { a: 42 }];
159}
160
161pub struct Foo { pub a: i32, pub b: i32 }
162"#,
163 );
164 }
165
166 #[test]
167 fn test_fill_struct_fields_empty() {
168 check_fix(
169 r#"
170struct TestStruct { one: i32, two: i64 }
171
172fn test_fn() {
173 let s = TestStruct {$0};
174}
175"#,
176 r#"
177struct TestStruct { one: i32, two: i64 }
178
179fn test_fn() {
180 let s = TestStruct { one: (), two: () };
181}
182"#,
183 );
184 }
185
186 #[test]
187 fn test_fill_struct_fields_self() {
188 check_fix(
189 r#"
190struct TestStruct { one: i32 }
191
192impl TestStruct {
193 fn test_fn() { let s = Self {$0}; }
194}
195"#,
196 r#"
197struct TestStruct { one: i32 }
198
199impl TestStruct {
200 fn test_fn() { let s = Self { one: () }; }
201}
202"#,
203 );
204 }
205
206 #[test]
207 fn test_fill_struct_fields_enum() {
208 check_fix(
209 r#"
210enum Expr {
211 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
212}
213
214impl Expr {
215 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
216 Expr::Bin {$0 }
217 }
218}
219"#,
220 r#"
221enum Expr {
222 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
223}
224
225impl Expr {
226 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
227 Expr::Bin { lhs: (), rhs: () }
228 }
229}
230"#,
231 );
232 }
233
234 #[test]
235 fn test_fill_struct_fields_partial() {
236 check_fix(
237 r#"
238struct TestStruct { one: i32, two: i64 }
239
240fn test_fn() {
241 let s = TestStruct{ two: 2$0 };
242}
243"#,
244 r"
245struct TestStruct { one: i32, two: i64 }
246
247fn test_fn() {
248 let s = TestStruct{ two: 2, one: () };
249}
250",
251 );
252 }
253
254 #[test]
255 fn test_fill_struct_fields_raw_ident() {
256 check_fix(
257 r#"
258struct TestStruct { r#type: u8 }
259
260fn test_fn() {
261 TestStruct { $0 };
262}
263"#,
264 r"
265struct TestStruct { r#type: u8 }
266
267fn test_fn() {
268 TestStruct { r#type: () };
269}
270",
271 );
272 }
273
274 #[test]
275 fn test_fill_struct_fields_no_diagnostic() {
276 check_diagnostics(
277 r#"
278struct TestStruct { one: i32, two: i64 }
279
280fn test_fn() {
281 let one = 1;
282 let s = TestStruct{ one, two: 2 };
283}
284 "#,
285 );
286 }
287
288 #[test]
289 fn test_fill_struct_fields_no_diagnostic_on_spread() {
290 check_diagnostics(
291 r#"
292struct TestStruct { one: i32, two: i64 }
293
294fn test_fn() {
295 let one = 1;
296 let s = TestStruct{ ..a };
297}
298"#,
299 );
300 }
301
302 #[test]
303 fn test_fill_struct_fields_blank_line() {
304 check_fix(
305 r#"
306struct S { a: (), b: () }
307
308fn f() {
309 S {
310 $0
311 };
312}
313"#,
314 r#"
315struct S { a: (), b: () }
316
317fn f() {
318 S {
319 a: (),
320 b: (),
321 };
322}
323"#,
324 );
325 }
326
327 #[test]
328 fn import_extern_crate_clash_with_inner_item() {
329 // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
330
331 check_diagnostics(
332 r#"
333//- /lib.rs crate:lib deps:jwt
334mod permissions;
335
336use permissions::jwt;
337
338fn f() {
339 fn inner() {}
340 jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
341}
342
343//- /permissions.rs
344pub mod jwt {
345 pub struct Claims {}
346}
347
348//- /jwt/lib.rs crate:jwt
349pub struct Claims {
350 field: u8,
351}
352 "#,
353 );
354 }
355}