diff options
Diffstat (limited to 'crates/ide_diagnostics/src/handlers/missing_fields.rs')
-rw-r--r-- | crates/ide_diagnostics/src/handlers/missing_fields.rs | 355 |
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 @@ | |||
1 | use either::Either; | ||
2 | use hir::{db::AstDatabase, InFile}; | ||
3 | use ide_db::{assists::Assist, source_change::SourceChange}; | ||
4 | use stdx::format_to; | ||
5 | use syntax::{algo, ast::make, AstNode, SyntaxNodePtr}; | ||
6 | use text_edit::TextEdit; | ||
7 | |||
8 | use 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 | // ``` | ||
21 | pub(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 | |||
39 | fn 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)] | ||
78 | mod tests { | ||
79 | use crate::tests::{check_diagnostics, check_fix}; | ||
80 | |||
81 | #[test] | ||
82 | fn missing_record_pat_field_diagnostic() { | ||
83 | check_diagnostics( | ||
84 | r#" | ||
85 | struct S { foo: i32, bar: () } | ||
86 | fn 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" | ||
99 | struct S { foo: i32, bar: () } | ||
100 | fn 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" | ||
113 | struct S { s: Box<u32> } | ||
114 | fn 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" | ||
125 | struct S { s: u32 } | ||
126 | fn 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#" | ||
138 | fn some() {} | ||
139 | fn items() {} | ||
140 | fn here() {} | ||
141 | |||
142 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
143 | |||
144 | fn main() { | ||
145 | let _x = id![Foo { a: $042 }]; | ||
146 | } | ||
147 | |||
148 | pub struct Foo { pub a: i32, pub b: i32 } | ||
149 | "#, | ||
150 | r#" | ||
151 | fn some(, b: () ) {} | ||
152 | fn items() {} | ||
153 | fn here() {} | ||
154 | |||
155 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
156 | |||
157 | fn main() { | ||
158 | let _x = id![Foo { a: 42 }]; | ||
159 | } | ||
160 | |||
161 | pub 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#" | ||
170 | struct TestStruct { one: i32, two: i64 } | ||
171 | |||
172 | fn test_fn() { | ||
173 | let s = TestStruct {$0}; | ||
174 | } | ||
175 | "#, | ||
176 | r#" | ||
177 | struct TestStruct { one: i32, two: i64 } | ||
178 | |||
179 | fn 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#" | ||
190 | struct TestStruct { one: i32 } | ||
191 | |||
192 | impl TestStruct { | ||
193 | fn test_fn() { let s = Self {$0}; } | ||
194 | } | ||
195 | "#, | ||
196 | r#" | ||
197 | struct TestStruct { one: i32 } | ||
198 | |||
199 | impl 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#" | ||
210 | enum Expr { | ||
211 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
212 | } | ||
213 | |||
214 | impl Expr { | ||
215 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | ||
216 | Expr::Bin {$0 } | ||
217 | } | ||
218 | } | ||
219 | "#, | ||
220 | r#" | ||
221 | enum Expr { | ||
222 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | ||
223 | } | ||
224 | |||
225 | impl 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#" | ||
238 | struct TestStruct { one: i32, two: i64 } | ||
239 | |||
240 | fn test_fn() { | ||
241 | let s = TestStruct{ two: 2$0 }; | ||
242 | } | ||
243 | "#, | ||
244 | r" | ||
245 | struct TestStruct { one: i32, two: i64 } | ||
246 | |||
247 | fn 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#" | ||
258 | struct TestStruct { r#type: u8 } | ||
259 | |||
260 | fn test_fn() { | ||
261 | TestStruct { $0 }; | ||
262 | } | ||
263 | "#, | ||
264 | r" | ||
265 | struct TestStruct { r#type: u8 } | ||
266 | |||
267 | fn 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#" | ||
278 | struct TestStruct { one: i32, two: i64 } | ||
279 | |||
280 | fn 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#" | ||
292 | struct TestStruct { one: i32, two: i64 } | ||
293 | |||
294 | fn 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#" | ||
306 | struct S { a: (), b: () } | ||
307 | |||
308 | fn f() { | ||
309 | S { | ||
310 | $0 | ||
311 | }; | ||
312 | } | ||
313 | "#, | ||
314 | r#" | ||
315 | struct S { a: (), b: () } | ||
316 | |||
317 | fn 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 | ||
334 | mod permissions; | ||
335 | |||
336 | use permissions::jwt; | ||
337 | |||
338 | fn 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 | ||
344 | pub mod jwt { | ||
345 | pub struct Claims {} | ||
346 | } | ||
347 | |||
348 | //- /jwt/lib.rs crate:jwt | ||
349 | pub struct Claims { | ||
350 | field: u8, | ||
351 | } | ||
352 | "#, | ||
353 | ); | ||
354 | } | ||
355 | } | ||