aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_ty/src/diagnostics.rs')
-rw-r--r--crates/hir_ty/src/diagnostics.rs403
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;
7use std::{any::Any, fmt}; 7use std::{any::Any, fmt};
8 8
9use base_db::CrateId; 9use base_db::CrateId;
10use hir_def::{DefWithBodyId, ModuleDefId}; 10use hir_def::ModuleDefId;
11use hir_expand::{name::Name, HirFileId, InFile}; 11use hir_expand::{HirFileId, InFile};
12use stdx::format_to;
13use syntax::{ast, AstPtr, SyntaxNodePtr}; 12use syntax::{ast, AstPtr, SyntaxNodePtr};
14 13
15use crate::{ 14use crate::{
@@ -18,7 +17,9 @@ use crate::{
18}; 17};
19 18
20pub use crate::diagnostics::{ 19pub 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
36pub 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)]
55pub 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
62impl 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)]
106pub 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
113impl 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)]
143pub struct MissingMatchArms {
144 pub file: HirFileId,
145 pub match_expr: AstPtr<ast::Expr>,
146 pub arms: AstPtr<ast::MatchArmList>,
147}
148
149impl 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)]
177pub 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
184impl 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)]
200pub struct RemoveThisSemicolon {
201 pub file: HirFileId,
202 pub expr: AstPtr<ast::Expr>,
203}
204
205impl 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)]
227pub struct MismatchedArgCount {
228 pub file: HirFileId,
229 pub call_expr: AstPtr<ast::Expr>,
230 pub expected: usize,
231 pub found: usize,
232}
233
234impl 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)]
254pub enum CaseType { 38pub 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)]
351pub 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
357impl 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)]
373mod tests { 132mod 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#"
462struct S { foo: i32, bar: () }
463fn 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"
476struct S { foo: i32, bar: () }
477fn 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"
490struct S { s: Box<u32> }
491fn 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"
502struct S { s: u32 }
503fn 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}