diff options
Diffstat (limited to 'crates/hir_ty/src/diagnostics.rs')
-rw-r--r-- | crates/hir_ty/src/diagnostics.rs | 403 |
1 files changed, 6 insertions, 397 deletions
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs index 12131d9bc..f3236bc06 100644 --- a/crates/hir_ty/src/diagnostics.rs +++ b/crates/hir_ty/src/diagnostics.rs | |||
@@ -7,9 +7,8 @@ mod decl_check; | |||
7 | use std::{any::Any, fmt}; | 7 | use std::{any::Any, fmt}; |
8 | 8 | ||
9 | use base_db::CrateId; | 9 | use base_db::CrateId; |
10 | use hir_def::{DefWithBodyId, ModuleDefId}; | 10 | use hir_def::ModuleDefId; |
11 | use hir_expand::{name::Name, HirFileId, InFile}; | 11 | use hir_expand::{HirFileId, InFile}; |
12 | use stdx::format_to; | ||
13 | use syntax::{ast, AstPtr, SyntaxNodePtr}; | 12 | use syntax::{ast, AstPtr, SyntaxNodePtr}; |
14 | 13 | ||
15 | use crate::{ | 14 | use crate::{ |
@@ -18,7 +17,9 @@ use crate::{ | |||
18 | }; | 17 | }; |
19 | 18 | ||
20 | pub use crate::diagnostics::{ | 19 | pub use crate::diagnostics::{ |
21 | expr::{record_literal_missing_fields, record_pattern_missing_fields}, | 20 | expr::{ |
21 | record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic, | ||
22 | }, | ||
22 | unsafe_check::missing_unsafe, | 23 | unsafe_check::missing_unsafe, |
23 | }; | 24 | }; |
24 | 25 | ||
@@ -33,223 +34,6 @@ pub fn validate_module_item( | |||
33 | validator.validate_item(owner); | 34 | validator.validate_item(owner); |
34 | } | 35 | } |
35 | 36 | ||
36 | pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) { | ||
37 | let _p = profile::span("validate_body"); | ||
38 | let infer = db.infer(owner); | ||
39 | let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink); | ||
40 | validator.validate_body(db); | ||
41 | } | ||
42 | |||
43 | // Diagnostic: missing-structure-fields | ||
44 | // | ||
45 | // This diagnostic is triggered if record lacks some fields that exist in the corresponding structure. | ||
46 | // | ||
47 | // Example: | ||
48 | // | ||
49 | // ```rust | ||
50 | // struct A { a: u8, b: u8 } | ||
51 | // | ||
52 | // let a = A { a: 10 }; | ||
53 | // ``` | ||
54 | #[derive(Debug)] | ||
55 | pub struct MissingFields { | ||
56 | pub file: HirFileId, | ||
57 | pub field_list_parent: AstPtr<ast::RecordExpr>, | ||
58 | pub field_list_parent_path: Option<AstPtr<ast::Path>>, | ||
59 | pub missed_fields: Vec<Name>, | ||
60 | } | ||
61 | |||
62 | impl Diagnostic for MissingFields { | ||
63 | fn code(&self) -> DiagnosticCode { | ||
64 | DiagnosticCode("missing-structure-fields") | ||
65 | } | ||
66 | fn message(&self) -> String { | ||
67 | let mut buf = String::from("Missing structure fields:\n"); | ||
68 | for field in &self.missed_fields { | ||
69 | format_to!(buf, "- {}\n", field); | ||
70 | } | ||
71 | buf | ||
72 | } | ||
73 | |||
74 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
75 | InFile { | ||
76 | file_id: self.file, | ||
77 | value: self | ||
78 | .field_list_parent_path | ||
79 | .clone() | ||
80 | .map(SyntaxNodePtr::from) | ||
81 | .unwrap_or_else(|| self.field_list_parent.clone().into()), | ||
82 | } | ||
83 | } | ||
84 | |||
85 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
86 | self | ||
87 | } | ||
88 | } | ||
89 | |||
90 | // Diagnostic: missing-pat-fields | ||
91 | // | ||
92 | // This diagnostic is triggered if pattern lacks some fields that exist in the corresponding structure. | ||
93 | // | ||
94 | // Example: | ||
95 | // | ||
96 | // ```rust | ||
97 | // struct A { a: u8, b: u8 } | ||
98 | // | ||
99 | // let a = A { a: 10, b: 20 }; | ||
100 | // | ||
101 | // if let A { a } = a { | ||
102 | // // ... | ||
103 | // } | ||
104 | // ``` | ||
105 | #[derive(Debug)] | ||
106 | pub struct MissingPatFields { | ||
107 | pub file: HirFileId, | ||
108 | pub field_list_parent: AstPtr<ast::RecordPat>, | ||
109 | pub field_list_parent_path: Option<AstPtr<ast::Path>>, | ||
110 | pub missed_fields: Vec<Name>, | ||
111 | } | ||
112 | |||
113 | impl Diagnostic for MissingPatFields { | ||
114 | fn code(&self) -> DiagnosticCode { | ||
115 | DiagnosticCode("missing-pat-fields") | ||
116 | } | ||
117 | fn message(&self) -> String { | ||
118 | let mut buf = String::from("Missing structure fields:\n"); | ||
119 | for field in &self.missed_fields { | ||
120 | format_to!(buf, "- {}\n", field); | ||
121 | } | ||
122 | buf | ||
123 | } | ||
124 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
125 | InFile { | ||
126 | file_id: self.file, | ||
127 | value: self | ||
128 | .field_list_parent_path | ||
129 | .clone() | ||
130 | .map(SyntaxNodePtr::from) | ||
131 | .unwrap_or_else(|| self.field_list_parent.clone().into()), | ||
132 | } | ||
133 | } | ||
134 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
135 | self | ||
136 | } | ||
137 | } | ||
138 | |||
139 | // Diagnostic: missing-match-arm | ||
140 | // | ||
141 | // This diagnostic is triggered if `match` block is missing one or more match arms. | ||
142 | #[derive(Debug)] | ||
143 | pub struct MissingMatchArms { | ||
144 | pub file: HirFileId, | ||
145 | pub match_expr: AstPtr<ast::Expr>, | ||
146 | pub arms: AstPtr<ast::MatchArmList>, | ||
147 | } | ||
148 | |||
149 | impl Diagnostic for MissingMatchArms { | ||
150 | fn code(&self) -> DiagnosticCode { | ||
151 | DiagnosticCode("missing-match-arm") | ||
152 | } | ||
153 | fn message(&self) -> String { | ||
154 | String::from("Missing match arm") | ||
155 | } | ||
156 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
157 | InFile { file_id: self.file, value: self.match_expr.clone().into() } | ||
158 | } | ||
159 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
160 | self | ||
161 | } | ||
162 | } | ||
163 | |||
164 | // Diagnostic: missing-ok-or-some-in-tail-expr | ||
165 | // | ||
166 | // This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`, | ||
167 | // or if a block that should return `Option` returns a value not wrapped in `Some`. | ||
168 | // | ||
169 | // Example: | ||
170 | // | ||
171 | // ```rust | ||
172 | // fn foo() -> Result<u8, ()> { | ||
173 | // 10 | ||
174 | // } | ||
175 | // ``` | ||
176 | #[derive(Debug)] | ||
177 | pub struct MissingOkOrSomeInTailExpr { | ||
178 | pub file: HirFileId, | ||
179 | pub expr: AstPtr<ast::Expr>, | ||
180 | // `Some` or `Ok` depending on whether the return type is Result or Option | ||
181 | pub required: String, | ||
182 | } | ||
183 | |||
184 | impl Diagnostic for MissingOkOrSomeInTailExpr { | ||
185 | fn code(&self) -> DiagnosticCode { | ||
186 | DiagnosticCode("missing-ok-or-some-in-tail-expr") | ||
187 | } | ||
188 | fn message(&self) -> String { | ||
189 | format!("wrap return expression in {}", self.required) | ||
190 | } | ||
191 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
192 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
193 | } | ||
194 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
195 | self | ||
196 | } | ||
197 | } | ||
198 | |||
199 | #[derive(Debug)] | ||
200 | pub struct RemoveThisSemicolon { | ||
201 | pub file: HirFileId, | ||
202 | pub expr: AstPtr<ast::Expr>, | ||
203 | } | ||
204 | |||
205 | impl Diagnostic for RemoveThisSemicolon { | ||
206 | fn code(&self) -> DiagnosticCode { | ||
207 | DiagnosticCode("remove-this-semicolon") | ||
208 | } | ||
209 | |||
210 | fn message(&self) -> String { | ||
211 | "Remove this semicolon".to_string() | ||
212 | } | ||
213 | |||
214 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
215 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
216 | } | ||
217 | |||
218 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
219 | self | ||
220 | } | ||
221 | } | ||
222 | |||
223 | // Diagnostic: mismatched-arg-count | ||
224 | // | ||
225 | // This diagnostic is triggered if a function is invoked with an incorrect amount of arguments. | ||
226 | #[derive(Debug)] | ||
227 | pub struct MismatchedArgCount { | ||
228 | pub file: HirFileId, | ||
229 | pub call_expr: AstPtr<ast::Expr>, | ||
230 | pub expected: usize, | ||
231 | pub found: usize, | ||
232 | } | ||
233 | |||
234 | impl Diagnostic for MismatchedArgCount { | ||
235 | fn code(&self) -> DiagnosticCode { | ||
236 | DiagnosticCode("mismatched-arg-count") | ||
237 | } | ||
238 | fn message(&self) -> String { | ||
239 | let s = if self.expected == 1 { "" } else { "s" }; | ||
240 | format!("Expected {} argument{}, found {}", self.expected, s, self.found) | ||
241 | } | ||
242 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
243 | InFile { file_id: self.file, value: self.call_expr.clone().into() } | ||
244 | } | ||
245 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
246 | self | ||
247 | } | ||
248 | fn is_experimental(&self) -> bool { | ||
249 | true | ||
250 | } | ||
251 | } | ||
252 | |||
253 | #[derive(Debug)] | 37 | #[derive(Debug)] |
254 | pub enum CaseType { | 38 | pub enum CaseType { |
255 | // `some_var` | 39 | // `some_var` |
@@ -344,31 +128,6 @@ impl Diagnostic for IncorrectCase { | |||
344 | } | 128 | } |
345 | } | 129 | } |
346 | 130 | ||
347 | // Diagnostic: replace-filter-map-next-with-find-map | ||
348 | // | ||
349 | // This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`. | ||
350 | #[derive(Debug)] | ||
351 | pub struct ReplaceFilterMapNextWithFindMap { | ||
352 | pub file: HirFileId, | ||
353 | /// This expression is the whole method chain up to and including `.filter_map(..).next()`. | ||
354 | pub next_expr: AstPtr<ast::Expr>, | ||
355 | } | ||
356 | |||
357 | impl Diagnostic for ReplaceFilterMapNextWithFindMap { | ||
358 | fn code(&self) -> DiagnosticCode { | ||
359 | DiagnosticCode("replace-filter-map-next-with-find-map") | ||
360 | } | ||
361 | fn message(&self) -> String { | ||
362 | "replace filter_map(..).next() with find_map(..)".to_string() | ||
363 | } | ||
364 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
365 | InFile { file_id: self.file, value: self.next_expr.clone().into() } | ||
366 | } | ||
367 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
368 | self | ||
369 | } | ||
370 | } | ||
371 | |||
372 | #[cfg(test)] | 131 | #[cfg(test)] |
373 | mod tests { | 132 | mod tests { |
374 | use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; | 133 | use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; |
@@ -378,7 +137,7 @@ mod tests { | |||
378 | use syntax::{TextRange, TextSize}; | 137 | use syntax::{TextRange, TextSize}; |
379 | 138 | ||
380 | use crate::{ | 139 | use crate::{ |
381 | diagnostics::{validate_body, validate_module_item}, | 140 | diagnostics::validate_module_item, |
382 | diagnostics_sink::{Diagnostic, DiagnosticSinkBuilder}, | 141 | diagnostics_sink::{Diagnostic, DiagnosticSinkBuilder}, |
383 | test_db::TestDB, | 142 | test_db::TestDB, |
384 | }; | 143 | }; |
@@ -416,11 +175,6 @@ mod tests { | |||
416 | } | 175 | } |
417 | } | 176 | } |
418 | } | 177 | } |
419 | |||
420 | for f in fns { | ||
421 | let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); | ||
422 | validate_body(self, f.into(), &mut sink); | ||
423 | } | ||
424 | } | 178 | } |
425 | } | 179 | } |
426 | } | 180 | } |
@@ -456,58 +210,6 @@ mod tests { | |||
456 | } | 210 | } |
457 | 211 | ||
458 | #[test] | 212 | #[test] |
459 | fn missing_record_pat_field_diagnostic() { | ||
460 | check_diagnostics( | ||
461 | r#" | ||
462 | struct S { foo: i32, bar: () } | ||
463 | fn baz(s: S) { | ||
464 | let S { foo: _ } = s; | ||
465 | //^ Missing structure fields: | ||
466 | //| - bar | ||
467 | } | ||
468 | "#, | ||
469 | ); | ||
470 | } | ||
471 | |||
472 | #[test] | ||
473 | fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { | ||
474 | check_diagnostics( | ||
475 | r" | ||
476 | struct S { foo: i32, bar: () } | ||
477 | fn baz(s: S) -> i32 { | ||
478 | match s { | ||
479 | S { foo, .. } => foo, | ||
480 | } | ||
481 | } | ||
482 | ", | ||
483 | ) | ||
484 | } | ||
485 | |||
486 | #[test] | ||
487 | fn missing_record_pat_field_box() { | ||
488 | check_diagnostics( | ||
489 | r" | ||
490 | struct S { s: Box<u32> } | ||
491 | fn x(a: S) { | ||
492 | let S { box s } = a; | ||
493 | } | ||
494 | ", | ||
495 | ) | ||
496 | } | ||
497 | |||
498 | #[test] | ||
499 | fn missing_record_pat_field_ref() { | ||
500 | check_diagnostics( | ||
501 | r" | ||
502 | struct S { s: u32 } | ||
503 | fn x(a: S) { | ||
504 | let S { ref s } = a; | ||
505 | } | ||
506 | ", | ||
507 | ) | ||
508 | } | ||
509 | |||
510 | #[test] | ||
511 | fn import_extern_crate_clash_with_inner_item() { | 213 | fn import_extern_crate_clash_with_inner_item() { |
512 | // This is more of a resolver test, but doesn't really work with the hir_def testsuite. | 214 | // This is more of a resolver test, but doesn't really work with the hir_def testsuite. |
513 | 215 | ||
@@ -535,97 +237,4 @@ pub struct Claims { | |||
535 | "#, | 237 | "#, |
536 | ); | 238 | ); |
537 | } | 239 | } |
538 | |||
539 | #[test] | ||
540 | fn missing_semicolon() { | ||
541 | check_diagnostics( | ||
542 | r#" | ||
543 | fn test() -> i32 { 123; } | ||
544 | //^^^ Remove this semicolon | ||
545 | "#, | ||
546 | ); | ||
547 | } | ||
548 | |||
549 | // Register the required standard library types to make the tests work | ||
550 | fn add_filter_map_with_find_next_boilerplate(body: &str) -> String { | ||
551 | let prefix = r#" | ||
552 | //- /main.rs crate:main deps:core | ||
553 | use core::iter::Iterator; | ||
554 | use core::option::Option::{self, Some, None}; | ||
555 | "#; | ||
556 | let suffix = r#" | ||
557 | //- /core/lib.rs crate:core | ||
558 | pub mod option { | ||
559 | pub enum Option<T> { Some(T), None } | ||
560 | } | ||
561 | pub mod iter { | ||
562 | pub trait Iterator { | ||
563 | type Item; | ||
564 | fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap } | ||
565 | fn next(&mut self) -> Option<Self::Item>; | ||
566 | } | ||
567 | pub struct FilterMap {} | ||
568 | impl Iterator for FilterMap { | ||
569 | type Item = i32; | ||
570 | fn next(&mut self) -> i32 { 7 } | ||
571 | } | ||
572 | } | ||
573 | "#; | ||
574 | format!("{}{}{}", prefix, body, suffix) | ||
575 | } | ||
576 | |||
577 | #[test] | ||
578 | fn replace_filter_map_next_with_find_map2() { | ||
579 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
580 | r#" | ||
581 | fn foo() { | ||
582 | let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next(); | ||
583 | //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..) | ||
584 | } | ||
585 | "#, | ||
586 | )); | ||
587 | } | ||
588 | |||
589 | #[test] | ||
590 | fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() { | ||
591 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
592 | r#" | ||
593 | fn foo() { | ||
594 | let m = [1, 2, 3] | ||
595 | .iter() | ||
596 | .filter_map(|x| if *x == 2 { Some (4) } else { None }) | ||
597 | .len(); | ||
598 | } | ||
599 | "#, | ||
600 | )); | ||
601 | } | ||
602 | |||
603 | #[test] | ||
604 | fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() { | ||
605 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
606 | r#" | ||
607 | fn foo() { | ||
608 | let m = [1, 2, 3] | ||
609 | .iter() | ||
610 | .filter_map(|x| if *x == 2 { Some (4) } else { None }) | ||
611 | .map(|x| x + 2) | ||
612 | .len(); | ||
613 | } | ||
614 | "#, | ||
615 | )); | ||
616 | } | ||
617 | |||
618 | #[test] | ||
619 | fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() { | ||
620 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
621 | r#" | ||
622 | fn foo() { | ||
623 | let m = [1, 2, 3] | ||
624 | .iter() | ||
625 | .filter_map(|x| if *x == 2 { Some (4) } else { None }); | ||
626 | let n = m.next(); | ||
627 | } | ||
628 | "#, | ||
629 | )); | ||
630 | } | ||
631 | } | 240 | } |