aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-06-12 17:28:19 +0100
committerAleksey Kladov <[email protected]>2021-06-12 19:00:22 +0100
commit6940cfed1e24a67e816e69e1093e04c0eb73e070 (patch)
tree68825f63dc6bdb571498ba659a2cf57bd31accb4 /crates
parent6f0141a14026827edfe4ef046013001acd22d94d (diff)
Move some hir_ty diagnostics to hir
Diffstat (limited to 'crates')
-rw-r--r--crates/hir/src/diagnostics.rs263
-rw-r--r--crates/hir/src/lib.rs175
-rw-r--r--crates/hir_ty/src/diagnostics.rs403
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs499
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs957
-rw-r--r--crates/hir_ty/src/diagnostics/match_check/deconstruct_pat.rs1
-rw-r--r--crates/ide/src/diagnostics.rs1304
7 files changed, 1821 insertions, 1781 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index e888fc23b..26dbcd86a 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -7,15 +7,12 @@ use std::any::Any;
7 7
8use cfg::{CfgExpr, CfgOptions, DnfExpr}; 8use cfg::{CfgExpr, CfgOptions, DnfExpr};
9use hir_def::path::ModPath; 9use hir_def::path::ModPath;
10use hir_expand::{HirFileId, InFile}; 10use hir_expand::{name::Name, HirFileId, InFile};
11use stdx::format_to; 11use stdx::format_to;
12use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; 12use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
13 13
14pub use hir_ty::{ 14pub use hir_ty::{
15 diagnostics::{ 15 diagnostics::IncorrectCase,
16 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms,
17 MissingOkOrSomeInTailExpr, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
18 },
19 diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder}, 16 diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder},
20}; 17};
21 18
@@ -325,3 +322,259 @@ impl Diagnostic for MissingUnsafe {
325 self 322 self
326 } 323 }
327} 324}
325
326// Diagnostic: missing-structure-fields
327//
328// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
329//
330// Example:
331//
332// ```rust
333// struct A { a: u8, b: u8 }
334//
335// let a = A { a: 10 };
336// ```
337#[derive(Debug)]
338pub struct MissingFields {
339 pub file: HirFileId,
340 pub field_list_parent: AstPtr<ast::RecordExpr>,
341 pub field_list_parent_path: Option<AstPtr<ast::Path>>,
342 pub missed_fields: Vec<Name>,
343}
344
345impl Diagnostic for MissingFields {
346 fn code(&self) -> DiagnosticCode {
347 DiagnosticCode("missing-structure-fields")
348 }
349 fn message(&self) -> String {
350 let mut buf = String::from("Missing structure fields:\n");
351 for field in &self.missed_fields {
352 format_to!(buf, "- {}\n", field);
353 }
354 buf
355 }
356
357 fn display_source(&self) -> InFile<SyntaxNodePtr> {
358 InFile {
359 file_id: self.file,
360 value: self
361 .field_list_parent_path
362 .clone()
363 .map(SyntaxNodePtr::from)
364 .unwrap_or_else(|| self.field_list_parent.clone().into()),
365 }
366 }
367
368 fn as_any(&self) -> &(dyn Any + Send + 'static) {
369 self
370 }
371}
372
373// Diagnostic: missing-pat-fields
374//
375// This diagnostic is triggered if pattern lacks some fields that exist in the corresponding structure.
376//
377// Example:
378//
379// ```rust
380// struct A { a: u8, b: u8 }
381//
382// let a = A { a: 10, b: 20 };
383//
384// if let A { a } = a {
385// // ...
386// }
387// ```
388#[derive(Debug)]
389pub struct MissingPatFields {
390 pub file: HirFileId,
391 pub field_list_parent: AstPtr<ast::RecordPat>,
392 pub field_list_parent_path: Option<AstPtr<ast::Path>>,
393 pub missed_fields: Vec<Name>,
394}
395
396impl Diagnostic for MissingPatFields {
397 fn code(&self) -> DiagnosticCode {
398 DiagnosticCode("missing-pat-fields")
399 }
400 fn message(&self) -> String {
401 let mut buf = String::from("Missing structure fields:\n");
402 for field in &self.missed_fields {
403 format_to!(buf, "- {}\n", field);
404 }
405 buf
406 }
407 fn display_source(&self) -> InFile<SyntaxNodePtr> {
408 InFile {
409 file_id: self.file,
410 value: self
411 .field_list_parent_path
412 .clone()
413 .map(SyntaxNodePtr::from)
414 .unwrap_or_else(|| self.field_list_parent.clone().into()),
415 }
416 }
417 fn as_any(&self) -> &(dyn Any + Send + 'static) {
418 self
419 }
420}
421
422// Diagnostic: replace-filter-map-next-with-find-map
423//
424// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
425#[derive(Debug)]
426pub struct ReplaceFilterMapNextWithFindMap {
427 pub file: HirFileId,
428 /// This expression is the whole method chain up to and including `.filter_map(..).next()`.
429 pub next_expr: AstPtr<ast::Expr>,
430}
431
432impl Diagnostic for ReplaceFilterMapNextWithFindMap {
433 fn code(&self) -> DiagnosticCode {
434 DiagnosticCode("replace-filter-map-next-with-find-map")
435 }
436 fn message(&self) -> String {
437 "replace filter_map(..).next() with find_map(..)".to_string()
438 }
439 fn display_source(&self) -> InFile<SyntaxNodePtr> {
440 InFile { file_id: self.file, value: self.next_expr.clone().into() }
441 }
442 fn as_any(&self) -> &(dyn Any + Send + 'static) {
443 self
444 }
445}
446
447// Diagnostic: mismatched-arg-count
448//
449// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
450#[derive(Debug)]
451pub struct MismatchedArgCount {
452 pub file: HirFileId,
453 pub call_expr: AstPtr<ast::Expr>,
454 pub expected: usize,
455 pub found: usize,
456}
457
458impl Diagnostic for MismatchedArgCount {
459 fn code(&self) -> DiagnosticCode {
460 DiagnosticCode("mismatched-arg-count")
461 }
462 fn message(&self) -> String {
463 let s = if self.expected == 1 { "" } else { "s" };
464 format!("Expected {} argument{}, found {}", self.expected, s, self.found)
465 }
466 fn display_source(&self) -> InFile<SyntaxNodePtr> {
467 InFile { file_id: self.file, value: self.call_expr.clone().into() }
468 }
469 fn as_any(&self) -> &(dyn Any + Send + 'static) {
470 self
471 }
472 fn is_experimental(&self) -> bool {
473 true
474 }
475}
476
477#[derive(Debug)]
478pub struct RemoveThisSemicolon {
479 pub file: HirFileId,
480 pub expr: AstPtr<ast::Expr>,
481}
482
483impl Diagnostic for RemoveThisSemicolon {
484 fn code(&self) -> DiagnosticCode {
485 DiagnosticCode("remove-this-semicolon")
486 }
487
488 fn message(&self) -> String {
489 "Remove this semicolon".to_string()
490 }
491
492 fn display_source(&self) -> InFile<SyntaxNodePtr> {
493 InFile { file_id: self.file, value: self.expr.clone().into() }
494 }
495
496 fn as_any(&self) -> &(dyn Any + Send + 'static) {
497 self
498 }
499}
500
501// Diagnostic: missing-ok-or-some-in-tail-expr
502//
503// This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`,
504// or if a block that should return `Option` returns a value not wrapped in `Some`.
505//
506// Example:
507//
508// ```rust
509// fn foo() -> Result<u8, ()> {
510// 10
511// }
512// ```
513#[derive(Debug)]
514pub struct MissingOkOrSomeInTailExpr {
515 pub file: HirFileId,
516 pub expr: AstPtr<ast::Expr>,
517 // `Some` or `Ok` depending on whether the return type is Result or Option
518 pub required: String,
519}
520
521impl Diagnostic for MissingOkOrSomeInTailExpr {
522 fn code(&self) -> DiagnosticCode {
523 DiagnosticCode("missing-ok-or-some-in-tail-expr")
524 }
525 fn message(&self) -> String {
526 format!("wrap return expression in {}", self.required)
527 }
528 fn display_source(&self) -> InFile<SyntaxNodePtr> {
529 InFile { file_id: self.file, value: self.expr.clone().into() }
530 }
531 fn as_any(&self) -> &(dyn Any + Send + 'static) {
532 self
533 }
534}
535
536// Diagnostic: missing-match-arm
537//
538// This diagnostic is triggered if `match` block is missing one or more match arms.
539#[derive(Debug)]
540pub struct MissingMatchArms {
541 pub file: HirFileId,
542 pub match_expr: AstPtr<ast::Expr>,
543 pub arms: AstPtr<ast::MatchArmList>,
544}
545
546impl Diagnostic for MissingMatchArms {
547 fn code(&self) -> DiagnosticCode {
548 DiagnosticCode("missing-match-arm")
549 }
550 fn message(&self) -> String {
551 String::from("Missing match arm")
552 }
553 fn display_source(&self) -> InFile<SyntaxNodePtr> {
554 InFile { file_id: self.file, value: self.match_expr.clone().into() }
555 }
556 fn as_any(&self) -> &(dyn Any + Send + 'static) {
557 self
558 }
559}
560
561#[derive(Debug)]
562pub struct InternalBailedOut {
563 pub file: HirFileId,
564 pub pat_syntax_ptr: SyntaxNodePtr,
565}
566
567impl Diagnostic for InternalBailedOut {
568 fn code(&self) -> DiagnosticCode {
569 DiagnosticCode("internal:match-check-bailed-out")
570 }
571 fn message(&self) -> String {
572 format!("Internal: match check bailed out")
573 }
574 fn display_source(&self) -> InFile<SyntaxNodePtr> {
575 InFile { file_id: self.file, value: self.pat_syntax_ptr.clone() }
576 }
577 fn as_any(&self) -> &(dyn Any + Send + 'static) {
578 self
579 }
580}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 8804e63c5..dd5515c2b 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -36,9 +36,11 @@ use std::{iter, sync::Arc};
36use arrayvec::ArrayVec; 36use arrayvec::ArrayVec;
37use base_db::{CrateDisplayName, CrateId, Edition, FileId}; 37use base_db::{CrateDisplayName, CrateId, Edition, FileId};
38use diagnostics::{ 38use diagnostics::{
39 BreakOutsideOfLoop, InactiveCode, MacroError, MissingUnsafe, NoSuchField, 39 BreakOutsideOfLoop, InactiveCode, InternalBailedOut, MacroError, MismatchedArgCount,
40 UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, 40 MissingFields, MissingOkOrSomeInTailExpr, MissingPatFields, MissingUnsafe, NoSuchField,
41 UnresolvedModule, UnresolvedProcMacro, 41 RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnimplementedBuiltinMacro,
42 UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
43 UnresolvedProcMacro,
42}; 44};
43use either::Either; 45use either::Either;
44use hir_def::{ 46use hir_def::{
@@ -61,6 +63,7 @@ use hir_ty::{
61 autoderef, 63 autoderef,
62 consteval::ConstExt, 64 consteval::ConstExt,
63 could_unify, 65 could_unify,
66 diagnostics::BodyValidationDiagnostic,
64 diagnostics_sink::DiagnosticSink, 67 diagnostics_sink::DiagnosticSink,
65 method_resolution::{self, def_crates, TyFingerprint}, 68 method_resolution::{self, def_crates, TyFingerprint},
66 primitive::UintTy, 69 primitive::UintTy,
@@ -82,7 +85,10 @@ use syntax::{
82}; 85};
83use tt::{Ident, Leaf, Literal, TokenTree}; 86use tt::{Ident, Leaf, Literal, TokenTree};
84 87
85use crate::db::{DefDatabase, HirDatabase}; 88use crate::{
89 db::{DefDatabase, HirDatabase},
90 diagnostics::MissingMatchArms,
91};
86 92
87pub use crate::{ 93pub use crate::{
88 attrs::{HasAttrs, Namespace}, 94 attrs::{HasAttrs, Namespace},
@@ -447,7 +453,12 @@ impl Module {
447 self.id.def_map(db.upcast())[self.id.local_id].scope.visibility_of(def.clone().into()) 453 self.id.def_map(db.upcast())[self.id.local_id].scope.visibility_of(def.clone().into())
448 } 454 }
449 455
450 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 456 pub fn diagnostics(
457 self,
458 db: &dyn HirDatabase,
459 sink: &mut DiagnosticSink,
460 internal_diagnostics: bool,
461 ) {
451 let _p = profile::span("Module::diagnostics").detail(|| { 462 let _p = profile::span("Module::diagnostics").detail(|| {
452 format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string())) 463 format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string()))
453 }); 464 });
@@ -593,11 +604,11 @@ impl Module {
593 } 604 }
594 for decl in self.declarations(db) { 605 for decl in self.declarations(db) {
595 match decl { 606 match decl {
596 crate::ModuleDef::Function(f) => f.diagnostics(db, sink), 607 crate::ModuleDef::Function(f) => f.diagnostics(db, sink, internal_diagnostics),
597 crate::ModuleDef::Module(m) => { 608 crate::ModuleDef::Module(m) => {
598 // Only add diagnostics from inline modules 609 // Only add diagnostics from inline modules
599 if def_map[m.id.local_id].origin.is_inline() { 610 if def_map[m.id.local_id].origin.is_inline() {
600 m.diagnostics(db, sink) 611 m.diagnostics(db, sink, internal_diagnostics)
601 } 612 }
602 } 613 }
603 _ => { 614 _ => {
@@ -609,7 +620,7 @@ impl Module {
609 for impl_def in self.impl_defs(db) { 620 for impl_def in self.impl_defs(db) {
610 for item in impl_def.items(db) { 621 for item in impl_def.items(db) {
611 if let AssocItem::Function(f) = item { 622 if let AssocItem::Function(f) = item {
612 f.diagnostics(db, sink); 623 f.diagnostics(db, sink, internal_diagnostics);
613 } 624 }
614 } 625 }
615 } 626 }
@@ -1011,7 +1022,12 @@ impl Function {
1011 db.function_data(self.id).is_async() 1022 db.function_data(self.id).is_async()
1012 } 1023 }
1013 1024
1014 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 1025 pub fn diagnostics(
1026 self,
1027 db: &dyn HirDatabase,
1028 sink: &mut DiagnosticSink,
1029 internal_diagnostics: bool,
1030 ) {
1015 let krate = self.module(db).id.krate(); 1031 let krate = self.module(db).id.krate();
1016 1032
1017 let source_map = db.body_with_source_map(self.id.into()).1; 1033 let source_map = db.body_with_source_map(self.id.into()).1;
@@ -1067,14 +1083,149 @@ impl Function {
1067 sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value }) 1083 sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
1068 } 1084 }
1069 Err(SyntheticSyntax) => { 1085 Err(SyntheticSyntax) => {
1070 // FIXME: The `expr` was desugared, report or assert that 1086 // FIXME: Here and eslwhere in this file, the `expr` was
1071 // this doesn't happen. 1087 // desugared, report or assert that this doesn't happen.
1088 }
1089 }
1090 }
1091
1092 for diagnostic in
1093 BodyValidationDiagnostic::collect(db, self.id.into(), internal_diagnostics)
1094 {
1095 match diagnostic {
1096 BodyValidationDiagnostic::RecordLiteralMissingFields {
1097 record_expr,
1098 variant,
1099 missed_fields,
1100 } => match source_map.expr_syntax(record_expr) {
1101 Ok(source_ptr) => {
1102 let root = source_ptr.file_syntax(db.upcast());
1103 if let ast::Expr::RecordExpr(record_expr) = &source_ptr.value.to_node(&root)
1104 {
1105 if let Some(_) = record_expr.record_expr_field_list() {
1106 let variant_data = variant.variant_data(db.upcast());
1107 let missed_fields = missed_fields
1108 .into_iter()
1109 .map(|idx| variant_data.fields()[idx].name.clone())
1110 .collect();
1111 sink.push(MissingFields {
1112 file: source_ptr.file_id,
1113 field_list_parent: AstPtr::new(&record_expr),
1114 field_list_parent_path: record_expr
1115 .path()
1116 .map(|path| AstPtr::new(&path)),
1117 missed_fields,
1118 })
1119 }
1120 }
1121 }
1122 Err(SyntheticSyntax) => (),
1123 },
1124 BodyValidationDiagnostic::RecordPatMissingFields {
1125 record_pat,
1126 variant,
1127 missed_fields,
1128 } => match source_map.pat_syntax(record_pat) {
1129 Ok(source_ptr) => {
1130 if let Some(expr) = source_ptr.value.as_ref().left() {
1131 let root = source_ptr.file_syntax(db.upcast());
1132 if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
1133 if let Some(_) = record_pat.record_pat_field_list() {
1134 let variant_data = variant.variant_data(db.upcast());
1135 let missed_fields = missed_fields
1136 .into_iter()
1137 .map(|idx| variant_data.fields()[idx].name.clone())
1138 .collect();
1139 sink.push(MissingPatFields {
1140 file: source_ptr.file_id,
1141 field_list_parent: AstPtr::new(&record_pat),
1142 field_list_parent_path: record_pat
1143 .path()
1144 .map(|path| AstPtr::new(&path)),
1145 missed_fields,
1146 })
1147 }
1148 }
1149 }
1150 }
1151 Err(SyntheticSyntax) => (),
1152 },
1153
1154 BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => {
1155 if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) {
1156 sink.push(ReplaceFilterMapNextWithFindMap {
1157 file: next_source_ptr.file_id,
1158 next_expr: next_source_ptr.value,
1159 });
1160 }
1161 }
1162 BodyValidationDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
1163 match source_map.expr_syntax(call_expr) {
1164 Ok(source_ptr) => sink.push(MismatchedArgCount {
1165 file: source_ptr.file_id,
1166 call_expr: source_ptr.value,
1167 expected,
1168 found,
1169 }),
1170 Err(SyntheticSyntax) => (),
1171 }
1172 }
1173 BodyValidationDiagnostic::RemoveThisSemicolon { expr } => {
1174 match source_map.expr_syntax(expr) {
1175 Ok(source_ptr) => sink.push(RemoveThisSemicolon {
1176 file: source_ptr.file_id,
1177 expr: source_ptr.value,
1178 }),
1179 Err(SyntheticSyntax) => (),
1180 }
1181 }
1182 BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr, required } => {
1183 match source_map.expr_syntax(expr) {
1184 Ok(source_ptr) => sink.push(MissingOkOrSomeInTailExpr {
1185 file: source_ptr.file_id,
1186 expr: source_ptr.value,
1187 required,
1188 }),
1189 Err(SyntheticSyntax) => (),
1190 }
1191 }
1192 BodyValidationDiagnostic::MissingMatchArms { match_expr } => {
1193 match source_map.expr_syntax(match_expr) {
1194 Ok(source_ptr) => {
1195 let root = source_ptr.file_syntax(db.upcast());
1196 if let ast::Expr::MatchExpr(match_expr) =
1197 &source_ptr.value.to_node(&root)
1198 {
1199 if let (Some(match_expr), Some(arms)) =
1200 (match_expr.expr(), match_expr.match_arm_list())
1201 {
1202 sink.push(MissingMatchArms {
1203 file: source_ptr.file_id,
1204 match_expr: AstPtr::new(&match_expr),
1205 arms: AstPtr::new(&arms),
1206 })
1207 }
1208 }
1209 }
1210 Err(SyntheticSyntax) => (),
1211 }
1212 }
1213 BodyValidationDiagnostic::InternalBailedOut { pat } => {
1214 match source_map.pat_syntax(pat) {
1215 Ok(source_ptr) => {
1216 let pat_syntax_ptr = source_ptr.value.either(Into::into, Into::into);
1217 sink.push(InternalBailedOut {
1218 file: source_ptr.file_id,
1219 pat_syntax_ptr,
1220 });
1221 }
1222 Err(SyntheticSyntax) => (),
1223 }
1072 } 1224 }
1073 } 1225 }
1074 } 1226 }
1075 1227
1076 hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink); 1228 hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink);
1077 hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
1078 } 1229 }
1079 1230
1080 /// Whether this function declaration has a definition. 1231 /// Whether this function declaration has a definition.
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}
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index a2a4d61db..c480ed352 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -9,19 +9,13 @@ use hir_def::{
9}; 9};
10use hir_expand::name; 10use hir_expand::name;
11use rustc_hash::FxHashSet; 11use rustc_hash::FxHashSet;
12use syntax::{ast, AstPtr};
13 12
14use crate::{ 13use crate::{
15 db::HirDatabase, 14 db::HirDatabase,
16 diagnostics::{ 15 diagnostics::match_check::{
17 match_check::{ 16 self,
18 self, 17 usefulness::{compute_match_usefulness, expand_pattern, MatchCheckCtx, PatternArena},
19 usefulness::{compute_match_usefulness, expand_pattern, MatchCheckCtx, PatternArena},
20 },
21 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
22 MissingPatFields, RemoveThisSemicolon,
23 }, 18 },
24 diagnostics_sink::DiagnosticSink,
25 AdtId, InferenceResult, Interner, TyExt, TyKind, 19 AdtId, InferenceResult, Interner, TyExt, TyKind,
26}; 20};
27 21
@@ -31,38 +25,81 @@ pub(crate) use hir_def::{
31 LocalFieldId, VariantId, 25 LocalFieldId, VariantId,
32}; 26};
33 27
34use super::ReplaceFilterMapNextWithFindMap; 28pub enum BodyValidationDiagnostic {
29 RecordLiteralMissingFields {
30 record_expr: ExprId,
31 variant: VariantId,
32 missed_fields: Vec<LocalFieldId>,
33 },
34 RecordPatMissingFields {
35 record_pat: PatId,
36 variant: VariantId,
37 missed_fields: Vec<LocalFieldId>,
38 },
39 ReplaceFilterMapNextWithFindMap {
40 method_call_expr: ExprId,
41 },
42 MismatchedArgCount {
43 call_expr: ExprId,
44 expected: usize,
45 found: usize,
46 },
47 RemoveThisSemicolon {
48 expr: ExprId,
49 },
50 MissingOkOrSomeInTailExpr {
51 expr: ExprId,
52 required: String,
53 },
54 MissingMatchArms {
55 match_expr: ExprId,
56 },
57 InternalBailedOut {
58 pat: PatId,
59 },
60}
35 61
36pub(super) struct ExprValidator<'a, 'b: 'a> { 62impl BodyValidationDiagnostic {
63 pub fn collect(
64 db: &dyn HirDatabase,
65 owner: DefWithBodyId,
66 internal_diagnostics: bool,
67 ) -> Vec<BodyValidationDiagnostic> {
68 let _p = profile::span("BodyValidationDiagnostic::collect");
69 let infer = db.infer(owner);
70 let mut validator = ExprValidator::new(owner, infer.clone());
71 validator.internal_diagnostics = internal_diagnostics;
72 validator.validate_body(db);
73 validator.diagnostics
74 }
75}
76
77struct ExprValidator {
37 owner: DefWithBodyId, 78 owner: DefWithBodyId,
38 infer: Arc<InferenceResult>, 79 infer: Arc<InferenceResult>,
39 sink: &'a mut DiagnosticSink<'b>, 80 pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
81 internal_diagnostics: bool,
40} 82}
41 83
42impl<'a, 'b> ExprValidator<'a, 'b> { 84impl ExprValidator {
43 pub(super) fn new( 85 fn new(owner: DefWithBodyId, infer: Arc<InferenceResult>) -> ExprValidator {
44 owner: DefWithBodyId, 86 ExprValidator { owner, infer, diagnostics: Vec::new(), internal_diagnostics: false }
45 infer: Arc<InferenceResult>,
46 sink: &'a mut DiagnosticSink<'b>,
47 ) -> ExprValidator<'a, 'b> {
48 ExprValidator { owner, infer, sink }
49 } 87 }
50 88
51 pub(super) fn validate_body(&mut self, db: &dyn HirDatabase) { 89 fn validate_body(&mut self, db: &dyn HirDatabase) {
52 self.check_for_filter_map_next(db); 90 self.check_for_filter_map_next(db);
53 91
54 let body = db.body(self.owner); 92 let body = db.body(self.owner);
55 93
56 for (id, expr) in body.exprs.iter() { 94 for (id, expr) in body.exprs.iter() {
57 if let Some((variant_def, missed_fields, true)) = 95 if let Some((variant, missed_fields, true)) =
58 record_literal_missing_fields(db, &self.infer, id, expr) 96 record_literal_missing_fields(db, &self.infer, id, expr)
59 { 97 {
60 self.create_record_literal_missing_fields_diagnostic( 98 self.diagnostics.push(BodyValidationDiagnostic::RecordLiteralMissingFields {
61 id, 99 record_expr: id,
62 db, 100 variant,
63 variant_def,
64 missed_fields, 101 missed_fields,
65 ); 102 });
66 } 103 }
67 104
68 match expr { 105 match expr {
@@ -76,15 +113,14 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
76 } 113 }
77 } 114 }
78 for (id, pat) in body.pats.iter() { 115 for (id, pat) in body.pats.iter() {
79 if let Some((variant_def, missed_fields, true)) = 116 if let Some((variant, missed_fields, true)) =
80 record_pattern_missing_fields(db, &self.infer, id, pat) 117 record_pattern_missing_fields(db, &self.infer, id, pat)
81 { 118 {
82 self.create_record_pattern_missing_fields_diagnostic( 119 self.diagnostics.push(BodyValidationDiagnostic::RecordPatMissingFields {
83 id, 120 record_pat: id,
84 db, 121 variant,
85 variant_def,
86 missed_fields, 122 missed_fields,
87 ); 123 });
88 } 124 }
89 } 125 }
90 let body_expr = &body[body.body_expr]; 126 let body_expr = &body[body.body_expr];
@@ -92,71 +128,7 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
92 if let Some(t) = tail { 128 if let Some(t) = tail {
93 self.validate_results_in_tail_expr(body.body_expr, *t, db); 129 self.validate_results_in_tail_expr(body.body_expr, *t, db);
94 } else if let Some(Statement::Expr { expr: id, .. }) = statements.last() { 130 } else if let Some(Statement::Expr { expr: id, .. }) = statements.last() {
95 self.validate_missing_tail_expr(body.body_expr, *id, db); 131 self.validate_missing_tail_expr(body.body_expr, *id);
96 }
97 }
98 }
99
100 fn create_record_literal_missing_fields_diagnostic(
101 &mut self,
102 id: ExprId,
103 db: &dyn HirDatabase,
104 variant_def: VariantId,
105 missed_fields: Vec<LocalFieldId>,
106 ) {
107 // XXX: only look at source_map if we do have missing fields
108 let (_, source_map) = db.body_with_source_map(self.owner);
109
110 if let Ok(source_ptr) = source_map.expr_syntax(id) {
111 let root = source_ptr.file_syntax(db.upcast());
112 if let ast::Expr::RecordExpr(record_expr) = &source_ptr.value.to_node(&root) {
113 if let Some(_) = record_expr.record_expr_field_list() {
114 let variant_data = variant_def.variant_data(db.upcast());
115 let missed_fields = missed_fields
116 .into_iter()
117 .map(|idx| variant_data.fields()[idx].name.clone())
118 .collect();
119 self.sink.push(MissingFields {
120 file: source_ptr.file_id,
121 field_list_parent: AstPtr::new(&record_expr),
122 field_list_parent_path: record_expr.path().map(|path| AstPtr::new(&path)),
123 missed_fields,
124 })
125 }
126 }
127 }
128 }
129
130 fn create_record_pattern_missing_fields_diagnostic(
131 &mut self,
132 id: PatId,
133 db: &dyn HirDatabase,
134 variant_def: VariantId,
135 missed_fields: Vec<LocalFieldId>,
136 ) {
137 // XXX: only look at source_map if we do have missing fields
138 let (_, source_map) = db.body_with_source_map(self.owner);
139
140 if let Ok(source_ptr) = source_map.pat_syntax(id) {
141 if let Some(expr) = source_ptr.value.as_ref().left() {
142 let root = source_ptr.file_syntax(db.upcast());
143 if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
144 if let Some(_) = record_pat.record_pat_field_list() {
145 let variant_data = variant_def.variant_data(db.upcast());
146 let missed_fields = missed_fields
147 .into_iter()
148 .map(|idx| variant_data.fields()[idx].name.clone())
149 .collect();
150 self.sink.push(MissingPatFields {
151 file: source_ptr.file_id,
152 field_list_parent: AstPtr::new(&record_pat),
153 field_list_parent_path: record_pat
154 .path()
155 .map(|path| AstPtr::new(&path)),
156 missed_fields,
157 })
158 }
159 }
160 } 132 }
161 } 133 }
162 } 134 }
@@ -199,13 +171,11 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
199 if function_id == *next_function_id { 171 if function_id == *next_function_id {
200 if let Some(filter_map_id) = prev { 172 if let Some(filter_map_id) = prev {
201 if *receiver == filter_map_id { 173 if *receiver == filter_map_id {
202 let (_, source_map) = db.body_with_source_map(self.owner); 174 self.diagnostics.push(
203 if let Ok(next_source_ptr) = source_map.expr_syntax(id) { 175 BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap {
204 self.sink.push(ReplaceFilterMapNextWithFindMap { 176 method_call_expr: id,
205 file: next_source_ptr.file_id, 177 },
206 next_expr: next_source_ptr.value, 178 );
207 });
208 }
209 } 179 }
210 } 180 }
211 } 181 }
@@ -266,19 +236,15 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
266 let mut arg_count = args.len(); 236 let mut arg_count = args.len();
267 237
268 if arg_count != param_count { 238 if arg_count != param_count {
269 let (_, source_map) = db.body_with_source_map(self.owner); 239 if is_method_call {
270 if let Ok(source_ptr) = source_map.expr_syntax(call_id) { 240 param_count -= 1;
271 if is_method_call { 241 arg_count -= 1;
272 param_count -= 1;
273 arg_count -= 1;
274 }
275 self.sink.push(MismatchedArgCount {
276 file: source_ptr.file_id,
277 call_expr: source_ptr.value,
278 expected: param_count,
279 found: arg_count,
280 });
281 } 242 }
243 self.diagnostics.push(BodyValidationDiagnostic::MismatchedArgCount {
244 call_expr: call_id,
245 expected: param_count,
246 found: arg_count,
247 });
282 } 248 }
283 } 249 }
284 250
@@ -346,8 +312,9 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
346 // fit the match expression, we skip this diagnostic. Skipping the entire 312 // fit the match expression, we skip this diagnostic. Skipping the entire
347 // diagnostic rather than just not including this match arm is preferred 313 // diagnostic rather than just not including this match arm is preferred
348 // to avoid the chance of false positives. 314 // to avoid the chance of false positives.
349 #[cfg(test)] 315 if self.internal_diagnostics {
350 match_check::tests::report_bail_out(db, self.owner, arm.pat, self.sink); 316 self.diagnostics.push(BodyValidationDiagnostic::InternalBailedOut { pat: arm.pat })
317 }
351 return; 318 return;
352 } 319 }
353 320
@@ -382,20 +349,7 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
382 // FIXME Report witnesses 349 // FIXME Report witnesses
383 // eprintln!("compute_match_usefulness(..) -> {:?}", &witnesses); 350 // eprintln!("compute_match_usefulness(..) -> {:?}", &witnesses);
384 if !witnesses.is_empty() { 351 if !witnesses.is_empty() {
385 if let Ok(source_ptr) = source_map.expr_syntax(id) { 352 self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms { match_expr: id });
386 let root = source_ptr.file_syntax(db.upcast());
387 if let ast::Expr::MatchExpr(match_expr) = &source_ptr.value.to_node(&root) {
388 if let (Some(match_expr), Some(arms)) =
389 (match_expr.expr(), match_expr.match_arm_list())
390 {
391 self.sink.push(MissingMatchArms {
392 file: source_ptr.file_id,
393 match_expr: AstPtr::new(&match_expr),
394 arms: AstPtr::new(&arms),
395 })
396 }
397 }
398 }
399 } 353 }
400 } 354 }
401 355
@@ -453,24 +407,12 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
453 if params.len(&Interner) > 0 407 if params.len(&Interner) > 0
454 && params.at(&Interner, 0).ty(&Interner) == Some(&mismatch.actual) 408 && params.at(&Interner, 0).ty(&Interner) == Some(&mismatch.actual)
455 { 409 {
456 let (_, source_map) = db.body_with_source_map(self.owner); 410 self.diagnostics
457 411 .push(BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr: id, required });
458 if let Ok(source_ptr) = source_map.expr_syntax(id) {
459 self.sink.push(MissingOkOrSomeInTailExpr {
460 file: source_ptr.file_id,
461 expr: source_ptr.value,
462 required,
463 });
464 }
465 } 412 }
466 } 413 }
467 414
468 fn validate_missing_tail_expr( 415 fn validate_missing_tail_expr(&mut self, body_id: ExprId, possible_tail_id: ExprId) {
469 &mut self,
470 body_id: ExprId,
471 possible_tail_id: ExprId,
472 db: &dyn HirDatabase,
473 ) {
474 let mismatch = match self.infer.type_mismatch_for_expr(body_id) { 416 let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
475 Some(m) => m, 417 Some(m) => m,
476 None => return, 418 None => return,
@@ -485,12 +427,8 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
485 return; 427 return;
486 } 428 }
487 429
488 let (_, source_map) = db.body_with_source_map(self.owner); 430 self.diagnostics
489 431 .push(BodyValidationDiagnostic::RemoveThisSemicolon { expr: possible_tail_id });
490 if let Ok(source_ptr) = source_map.expr_syntax(possible_tail_id) {
491 self.sink
492 .push(RemoveThisSemicolon { file: source_ptr.file_id, expr: source_ptr.value });
493 }
494 } 432 }
495} 433}
496 434
@@ -568,258 +506,3 @@ fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResul
568 walk(pat, body, infer, &mut has_type_mismatches); 506 walk(pat, body, infer, &mut has_type_mismatches);
569 !has_type_mismatches 507 !has_type_mismatches
570} 508}
571
572#[cfg(test)]
573mod tests {
574 use crate::diagnostics::tests::check_diagnostics;
575
576 #[test]
577 fn simple_free_fn_zero() {
578 check_diagnostics(
579 r#"
580fn zero() {}
581fn f() { zero(1); }
582 //^^^^^^^ Expected 0 arguments, found 1
583"#,
584 );
585
586 check_diagnostics(
587 r#"
588fn zero() {}
589fn f() { zero(); }
590"#,
591 );
592 }
593
594 #[test]
595 fn simple_free_fn_one() {
596 check_diagnostics(
597 r#"
598fn one(arg: u8) {}
599fn f() { one(); }
600 //^^^^^ Expected 1 argument, found 0
601"#,
602 );
603
604 check_diagnostics(
605 r#"
606fn one(arg: u8) {}
607fn f() { one(1); }
608"#,
609 );
610 }
611
612 #[test]
613 fn method_as_fn() {
614 check_diagnostics(
615 r#"
616struct S;
617impl S { fn method(&self) {} }
618
619fn f() {
620 S::method();
621} //^^^^^^^^^^^ Expected 1 argument, found 0
622"#,
623 );
624
625 check_diagnostics(
626 r#"
627struct S;
628impl S { fn method(&self) {} }
629
630fn f() {
631 S::method(&S);
632 S.method();
633}
634"#,
635 );
636 }
637
638 #[test]
639 fn method_with_arg() {
640 check_diagnostics(
641 r#"
642struct S;
643impl S { fn method(&self, arg: u8) {} }
644
645 fn f() {
646 S.method();
647 } //^^^^^^^^^^ Expected 1 argument, found 0
648 "#,
649 );
650
651 check_diagnostics(
652 r#"
653struct S;
654impl S { fn method(&self, arg: u8) {} }
655
656fn f() {
657 S::method(&S, 0);
658 S.method(1);
659}
660"#,
661 );
662 }
663
664 #[test]
665 fn method_unknown_receiver() {
666 // note: this is incorrect code, so there might be errors on this in the
667 // future, but we shouldn't emit an argument count diagnostic here
668 check_diagnostics(
669 r#"
670trait Foo { fn method(&self, arg: usize) {} }
671
672fn f() {
673 let x;
674 x.method();
675}
676"#,
677 );
678 }
679
680 #[test]
681 fn tuple_struct() {
682 check_diagnostics(
683 r#"
684struct Tup(u8, u16);
685fn f() {
686 Tup(0);
687} //^^^^^^ Expected 2 arguments, found 1
688"#,
689 )
690 }
691
692 #[test]
693 fn enum_variant() {
694 check_diagnostics(
695 r#"
696enum En { Variant(u8, u16), }
697fn f() {
698 En::Variant(0);
699} //^^^^^^^^^^^^^^ Expected 2 arguments, found 1
700"#,
701 )
702 }
703
704 #[test]
705 fn enum_variant_type_macro() {
706 check_diagnostics(
707 r#"
708macro_rules! Type {
709 () => { u32 };
710}
711enum Foo {
712 Bar(Type![])
713}
714impl Foo {
715 fn new() {
716 Foo::Bar(0);
717 Foo::Bar(0, 1);
718 //^^^^^^^^^^^^^^ Expected 1 argument, found 2
719 Foo::Bar();
720 //^^^^^^^^^^ Expected 1 argument, found 0
721 }
722}
723 "#,
724 );
725 }
726
727 #[test]
728 fn varargs() {
729 check_diagnostics(
730 r#"
731extern "C" {
732 fn fixed(fixed: u8);
733 fn varargs(fixed: u8, ...);
734 fn varargs2(...);
735}
736
737fn f() {
738 unsafe {
739 fixed(0);
740 fixed(0, 1);
741 //^^^^^^^^^^^ Expected 1 argument, found 2
742 varargs(0);
743 varargs(0, 1);
744 varargs2();
745 varargs2(0);
746 varargs2(0, 1);
747 }
748}
749 "#,
750 )
751 }
752
753 #[test]
754 fn arg_count_lambda() {
755 check_diagnostics(
756 r#"
757fn main() {
758 let f = |()| ();
759 f();
760 //^^^ Expected 1 argument, found 0
761 f(());
762 f((), ());
763 //^^^^^^^^^ Expected 1 argument, found 2
764}
765"#,
766 )
767 }
768
769 #[test]
770 fn cfgd_out_call_arguments() {
771 check_diagnostics(
772 r#"
773struct C(#[cfg(FALSE)] ());
774impl C {
775 fn new() -> Self {
776 Self(
777 #[cfg(FALSE)]
778 (),
779 )
780 }
781
782 fn method(&self) {}
783}
784
785fn main() {
786 C::new().method(#[cfg(FALSE)] 0);
787}
788 "#,
789 );
790 }
791
792 #[test]
793 fn cfgd_out_fn_params() {
794 check_diagnostics(
795 r#"
796fn foo(#[cfg(NEVER)] x: ()) {}
797
798struct S;
799
800impl S {
801 fn method(#[cfg(NEVER)] self) {}
802 fn method2(#[cfg(NEVER)] self, arg: u8) {}
803 fn method3(self, #[cfg(NEVER)] arg: u8) {}
804}
805
806extern "C" {
807 fn fixed(fixed: u8, #[cfg(NEVER)] ...);
808 fn varargs(#[cfg(not(NEVER))] ...);
809}
810
811fn main() {
812 foo();
813 S::method();
814 S::method2(0);
815 S::method3(S);
816 S.method3();
817 unsafe {
818 fixed(0);
819 varargs(1, 2, 3);
820 }
821}
822 "#,
823 )
824 }
825}
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs
index c8e1b23de..a30e42699 100644
--- a/crates/hir_ty/src/diagnostics/match_check.rs
+++ b/crates/hir_ty/src/diagnostics/match_check.rs
@@ -364,960 +364,3 @@ impl PatternFoldable for PatKind {
364 } 364 }
365 } 365 }
366} 366}
367
368#[cfg(test)]
369pub(super) mod tests {
370 mod report {
371 use std::any::Any;
372
373 use hir_def::{expr::PatId, DefWithBodyId};
374 use hir_expand::{HirFileId, InFile};
375 use syntax::SyntaxNodePtr;
376
377 use crate::{
378 db::HirDatabase,
379 diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
380 };
381
382 /// In tests, match check bails out loudly.
383 /// This helps to catch incorrect tests that pass due to false negatives.
384 pub(crate) fn report_bail_out(
385 db: &dyn HirDatabase,
386 def: DefWithBodyId,
387 pat: PatId,
388 sink: &mut DiagnosticSink,
389 ) {
390 let (_, source_map) = db.body_with_source_map(def);
391 if let Ok(source_ptr) = source_map.pat_syntax(pat) {
392 let pat_syntax_ptr = source_ptr.value.either(Into::into, Into::into);
393 sink.push(BailedOut { file: source_ptr.file_id, pat_syntax_ptr });
394 }
395 }
396
397 #[derive(Debug)]
398 struct BailedOut {
399 file: HirFileId,
400 pat_syntax_ptr: SyntaxNodePtr,
401 }
402
403 impl Diagnostic for BailedOut {
404 fn code(&self) -> DiagnosticCode {
405 DiagnosticCode("internal:match-check-bailed-out")
406 }
407 fn message(&self) -> String {
408 format!("Internal: match check bailed out")
409 }
410 fn display_source(&self) -> InFile<SyntaxNodePtr> {
411 InFile { file_id: self.file, value: self.pat_syntax_ptr.clone() }
412 }
413 fn as_any(&self) -> &(dyn Any + Send + 'static) {
414 self
415 }
416 }
417 }
418
419 use crate::diagnostics::tests::check_diagnostics;
420
421 pub(crate) use self::report::report_bail_out;
422
423 #[test]
424 fn empty_tuple() {
425 check_diagnostics(
426 r#"
427fn main() {
428 match () { }
429 //^^ Missing match arm
430 match (()) { }
431 //^^^^ Missing match arm
432
433 match () { _ => (), }
434 match () { () => (), }
435 match (()) { (()) => (), }
436}
437"#,
438 );
439 }
440
441 #[test]
442 fn tuple_of_two_empty_tuple() {
443 check_diagnostics(
444 r#"
445fn main() {
446 match ((), ()) { }
447 //^^^^^^^^ Missing match arm
448
449 match ((), ()) { ((), ()) => (), }
450}
451"#,
452 );
453 }
454
455 #[test]
456 fn boolean() {
457 check_diagnostics(
458 r#"
459fn test_main() {
460 match false { }
461 //^^^^^ Missing match arm
462 match false { true => (), }
463 //^^^^^ Missing match arm
464 match (false, true) {}
465 //^^^^^^^^^^^^^ Missing match arm
466 match (false, true) { (true, true) => (), }
467 //^^^^^^^^^^^^^ Missing match arm
468 match (false, true) {
469 //^^^^^^^^^^^^^ Missing match arm
470 (false, true) => (),
471 (false, false) => (),
472 (true, false) => (),
473 }
474 match (false, true) { (true, _x) => (), }
475 //^^^^^^^^^^^^^ Missing match arm
476
477 match false { true => (), false => (), }
478 match (false, true) {
479 (false, _) => (),
480 (true, false) => (),
481 (_, true) => (),
482 }
483 match (false, true) {
484 (true, true) => (),
485 (true, false) => (),
486 (false, true) => (),
487 (false, false) => (),
488 }
489 match (false, true) {
490 (true, _x) => (),
491 (false, true) => (),
492 (false, false) => (),
493 }
494 match (false, true, false) {
495 (false, ..) => (),
496 (true, ..) => (),
497 }
498 match (false, true, false) {
499 (.., false) => (),
500 (.., true) => (),
501 }
502 match (false, true, false) { (..) => (), }
503}
504"#,
505 );
506 }
507
508 #[test]
509 fn tuple_of_tuple_and_bools() {
510 check_diagnostics(
511 r#"
512fn main() {
513 match (false, ((), false)) {}
514 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
515 match (false, ((), false)) { (true, ((), true)) => (), }
516 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
517 match (false, ((), false)) { (true, _) => (), }
518 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
519
520 match (false, ((), false)) {
521 (true, ((), true)) => (),
522 (true, ((), false)) => (),
523 (false, ((), true)) => (),
524 (false, ((), false)) => (),
525 }
526 match (false, ((), false)) {
527 (true, ((), true)) => (),
528 (true, ((), false)) => (),
529 (false, _) => (),
530 }
531}
532"#,
533 );
534 }
535
536 #[test]
537 fn enums() {
538 check_diagnostics(
539 r#"
540enum Either { A, B, }
541
542fn main() {
543 match Either::A { }
544 //^^^^^^^^^ Missing match arm
545 match Either::B { Either::A => (), }
546 //^^^^^^^^^ Missing match arm
547
548 match &Either::B {
549 //^^^^^^^^^^ Missing match arm
550 Either::A => (),
551 }
552
553 match Either::B {
554 Either::A => (), Either::B => (),
555 }
556 match &Either::B {
557 Either::A => (), Either::B => (),
558 }
559}
560"#,
561 );
562 }
563
564 #[test]
565 fn enum_containing_bool() {
566 check_diagnostics(
567 r#"
568enum Either { A(bool), B }
569
570fn main() {
571 match Either::B { }
572 //^^^^^^^^^ Missing match arm
573 match Either::B {
574 //^^^^^^^^^ Missing match arm
575 Either::A(true) => (), Either::B => ()
576 }
577
578 match Either::B {
579 Either::A(true) => (),
580 Either::A(false) => (),
581 Either::B => (),
582 }
583 match Either::B {
584 Either::B => (),
585 _ => (),
586 }
587 match Either::B {
588 Either::A(_) => (),
589 Either::B => (),
590 }
591
592}
593 "#,
594 );
595 }
596
597 #[test]
598 fn enum_different_sizes() {
599 check_diagnostics(
600 r#"
601enum Either { A(bool), B(bool, bool) }
602
603fn main() {
604 match Either::A(false) {
605 //^^^^^^^^^^^^^^^^ Missing match arm
606 Either::A(_) => (),
607 Either::B(false, _) => (),
608 }
609
610 match Either::A(false) {
611 Either::A(_) => (),
612 Either::B(true, _) => (),
613 Either::B(false, _) => (),
614 }
615 match Either::A(false) {
616 Either::A(true) | Either::A(false) => (),
617 Either::B(true, _) => (),
618 Either::B(false, _) => (),
619 }
620}
621"#,
622 );
623 }
624
625 #[test]
626 fn tuple_of_enum_no_diagnostic() {
627 check_diagnostics(
628 r#"
629enum Either { A(bool), B(bool, bool) }
630enum Either2 { C, D }
631
632fn main() {
633 match (Either::A(false), Either2::C) {
634 (Either::A(true), _) | (Either::A(false), _) => (),
635 (Either::B(true, _), Either2::C) => (),
636 (Either::B(false, _), Either2::C) => (),
637 (Either::B(_, _), Either2::D) => (),
638 }
639}
640"#,
641 );
642 }
643
644 #[test]
645 fn or_pattern_no_diagnostic() {
646 check_diagnostics(
647 r#"
648enum Either {A, B}
649
650fn main() {
651 match (Either::A, Either::B) {
652 (Either::A | Either::B, _) => (),
653 }
654}"#,
655 )
656 }
657
658 #[test]
659 fn mismatched_types() {
660 // Match statements with arms that don't match the
661 // expression pattern do not fire this diagnostic.
662 check_diagnostics(
663 r#"
664enum Either { A, B }
665enum Either2 { C, D }
666
667fn main() {
668 match Either::A {
669 Either2::C => (),
670 // ^^^^^^^^^^ Internal: match check bailed out
671 Either2::D => (),
672 }
673 match (true, false) {
674 (true, false, true) => (),
675 // ^^^^^^^^^^^^^^^^^^^ Internal: match check bailed out
676 (true) => (),
677 }
678 match (true, false) { (true,) => {} }
679 // ^^^^^^^ Internal: match check bailed out
680 match (0) { () => () }
681 // ^^ Internal: match check bailed out
682 match Unresolved::Bar { Unresolved::Baz => () }
683}
684 "#,
685 );
686 }
687
688 #[test]
689 fn mismatched_types_in_or_patterns() {
690 check_diagnostics(
691 r#"
692fn main() {
693 match false { true | () => {} }
694 // ^^^^^^^^^ Internal: match check bailed out
695 match (false,) { (true | (),) => {} }
696 // ^^^^^^^^^^^^ Internal: match check bailed out
697}
698"#,
699 );
700 }
701
702 #[test]
703 fn malformed_match_arm_tuple_enum_missing_pattern() {
704 // We are testing to be sure we don't panic here when the match
705 // arm `Either::B` is missing its pattern.
706 check_diagnostics(
707 r#"
708enum Either { A, B(u32) }
709
710fn main() {
711 match Either::A {
712 Either::A => (),
713 Either::B() => (),
714 }
715}
716"#,
717 );
718 }
719
720 #[test]
721 fn malformed_match_arm_extra_fields() {
722 check_diagnostics(
723 r#"
724enum A { B(isize, isize), C }
725fn main() {
726 match A::B(1, 2) {
727 A::B(_, _, _) => (),
728 // ^^^^^^^^^^^^^ Internal: match check bailed out
729 }
730 match A::B(1, 2) {
731 A::C(_) => (),
732 // ^^^^^^^ Internal: match check bailed out
733 }
734}
735"#,
736 );
737 }
738
739 #[test]
740 fn expr_diverges() {
741 check_diagnostics(
742 r#"
743enum Either { A, B }
744
745fn main() {
746 match loop {} {
747 Either::A => (),
748 // ^^^^^^^^^ Internal: match check bailed out
749 Either::B => (),
750 }
751 match loop {} {
752 Either::A => (),
753 // ^^^^^^^^^ Internal: match check bailed out
754 }
755 match loop { break Foo::A } {
756 //^^^^^^^^^^^^^^^^^^^^^ Missing match arm
757 Either::A => (),
758 }
759 match loop { break Foo::A } {
760 Either::A => (),
761 Either::B => (),
762 }
763}
764"#,
765 );
766 }
767
768 #[test]
769 fn expr_partially_diverges() {
770 check_diagnostics(
771 r#"
772enum Either<T> { A(T), B }
773
774fn foo() -> Either<!> { Either::B }
775fn main() -> u32 {
776 match foo() {
777 Either::A(val) => val,
778 Either::B => 0,
779 }
780}
781"#,
782 );
783 }
784
785 #[test]
786 fn enum_record() {
787 check_diagnostics(
788 r#"
789enum Either { A { foo: bool }, B }
790
791fn main() {
792 let a = Either::A { foo: true };
793 match a { }
794 //^ Missing match arm
795 match a { Either::A { foo: true } => () }
796 //^ Missing match arm
797 match a {
798 Either::A { } => (),
799 //^^^^^^^^^ Missing structure fields:
800 // | - foo
801 Either::B => (),
802 }
803 match a {
804 //^ Missing match arm
805 Either::A { } => (),
806 } //^^^^^^^^^ Missing structure fields:
807 // | - foo
808
809 match a {
810 Either::A { foo: true } => (),
811 Either::A { foo: false } => (),
812 Either::B => (),
813 }
814 match a {
815 Either::A { foo: _ } => (),
816 Either::B => (),
817 }
818}
819"#,
820 );
821 }
822
823 #[test]
824 fn enum_record_fields_out_of_order() {
825 check_diagnostics(
826 r#"
827enum Either {
828 A { foo: bool, bar: () },
829 B,
830}
831
832fn main() {
833 let a = Either::A { foo: true, bar: () };
834 match a {
835 //^ Missing match arm
836 Either::A { bar: (), foo: false } => (),
837 Either::A { foo: true, bar: () } => (),
838 }
839
840 match a {
841 Either::A { bar: (), foo: false } => (),
842 Either::A { foo: true, bar: () } => (),
843 Either::B => (),
844 }
845}
846"#,
847 );
848 }
849
850 #[test]
851 fn enum_record_ellipsis() {
852 check_diagnostics(
853 r#"
854enum Either {
855 A { foo: bool, bar: bool },
856 B,
857}
858
859fn main() {
860 let a = Either::B;
861 match a {
862 //^ Missing match arm
863 Either::A { foo: true, .. } => (),
864 Either::B => (),
865 }
866 match a {
867 //^ Missing match arm
868 Either::A { .. } => (),
869 }
870
871 match a {
872 Either::A { foo: true, .. } => (),
873 Either::A { foo: false, .. } => (),
874 Either::B => (),
875 }
876
877 match a {
878 Either::A { .. } => (),
879 Either::B => (),
880 }
881}
882"#,
883 );
884 }
885
886 #[test]
887 fn enum_tuple_partial_ellipsis() {
888 check_diagnostics(
889 r#"
890enum Either {
891 A(bool, bool, bool, bool),
892 B,
893}
894
895fn main() {
896 match Either::B {
897 //^^^^^^^^^ Missing match arm
898 Either::A(true, .., true) => (),
899 Either::A(true, .., false) => (),
900 Either::A(false, .., false) => (),
901 Either::B => (),
902 }
903 match Either::B {
904 //^^^^^^^^^ Missing match arm
905 Either::A(true, .., true) => (),
906 Either::A(true, .., false) => (),
907 Either::A(.., true) => (),
908 Either::B => (),
909 }
910
911 match Either::B {
912 Either::A(true, .., true) => (),
913 Either::A(true, .., false) => (),
914 Either::A(false, .., true) => (),
915 Either::A(false, .., false) => (),
916 Either::B => (),
917 }
918 match Either::B {
919 Either::A(true, .., true) => (),
920 Either::A(true, .., false) => (),
921 Either::A(.., true) => (),
922 Either::A(.., false) => (),
923 Either::B => (),
924 }
925}
926"#,
927 );
928 }
929
930 #[test]
931 fn never() {
932 check_diagnostics(
933 r#"
934enum Never {}
935
936fn enum_(never: Never) {
937 match never {}
938}
939fn enum_ref(never: &Never) {
940 match never {}
941 //^^^^^ Missing match arm
942}
943fn bang(never: !) {
944 match never {}
945}
946"#,
947 );
948 }
949
950 #[test]
951 fn unknown_type() {
952 check_diagnostics(
953 r#"
954enum Option<T> { Some(T), None }
955
956fn main() {
957 // `Never` is deliberately not defined so that it's an uninferred type.
958 match Option::<Never>::None {
959 None => (),
960 Some(never) => match never {},
961 // ^^^^^^^^^^^ Internal: match check bailed out
962 }
963 match Option::<Never>::None {
964 //^^^^^^^^^^^^^^^^^^^^^ Missing match arm
965 Option::Some(_never) => {},
966 }
967}
968"#,
969 );
970 }
971
972 #[test]
973 fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
974 check_diagnostics(
975 r#"
976fn main() {
977 match (false, true, false) {
978 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
979 (false, ..) => (),
980 }
981}"#,
982 );
983 }
984
985 #[test]
986 fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() {
987 check_diagnostics(
988 r#"
989fn main() {
990 match (false, true, false) {
991 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
992 (.., false) => (),
993 }
994}"#,
995 );
996 }
997
998 #[test]
999 fn tuple_of_bools_with_ellipsis_in_middle_missing_arm() {
1000 check_diagnostics(
1001 r#"
1002fn main() {
1003 match (false, true, false) {
1004 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1005 (true, .., false) => (),
1006 }
1007}"#,
1008 );
1009 }
1010
1011 #[test]
1012 fn record_struct() {
1013 check_diagnostics(
1014 r#"struct Foo { a: bool }
1015fn main(f: Foo) {
1016 match f {}
1017 //^ Missing match arm
1018 match f { Foo { a: true } => () }
1019 //^ Missing match arm
1020 match &f { Foo { a: true } => () }
1021 //^^ Missing match arm
1022 match f { Foo { a: _ } => () }
1023 match f {
1024 Foo { a: true } => (),
1025 Foo { a: false } => (),
1026 }
1027 match &f {
1028 Foo { a: true } => (),
1029 Foo { a: false } => (),
1030 }
1031}
1032"#,
1033 );
1034 }
1035
1036 #[test]
1037 fn tuple_struct() {
1038 check_diagnostics(
1039 r#"struct Foo(bool);
1040fn main(f: Foo) {
1041 match f {}
1042 //^ Missing match arm
1043 match f { Foo(true) => () }
1044 //^ Missing match arm
1045 match f {
1046 Foo(true) => (),
1047 Foo(false) => (),
1048 }
1049}
1050"#,
1051 );
1052 }
1053
1054 #[test]
1055 fn unit_struct() {
1056 check_diagnostics(
1057 r#"struct Foo;
1058fn main(f: Foo) {
1059 match f {}
1060 //^ Missing match arm
1061 match f { Foo => () }
1062}
1063"#,
1064 );
1065 }
1066
1067 #[test]
1068 fn record_struct_ellipsis() {
1069 check_diagnostics(
1070 r#"struct Foo { foo: bool, bar: bool }
1071fn main(f: Foo) {
1072 match f { Foo { foo: true, .. } => () }
1073 //^ Missing match arm
1074 match f {
1075 //^ Missing match arm
1076 Foo { foo: true, .. } => (),
1077 Foo { bar: false, .. } => ()
1078 }
1079 match f { Foo { .. } => () }
1080 match f {
1081 Foo { foo: true, .. } => (),
1082 Foo { foo: false, .. } => ()
1083 }
1084}
1085"#,
1086 );
1087 }
1088
1089 #[test]
1090 fn internal_or() {
1091 check_diagnostics(
1092 r#"
1093fn main() {
1094 enum Either { A(bool), B }
1095 match Either::B {
1096 //^^^^^^^^^ Missing match arm
1097 Either::A(true | false) => (),
1098 }
1099}
1100"#,
1101 );
1102 }
1103
1104 #[test]
1105 fn no_panic_at_unimplemented_subpattern_type() {
1106 check_diagnostics(
1107 r#"
1108struct S { a: char}
1109fn main(v: S) {
1110 match v { S{ a } => {} }
1111 match v { S{ a: _x } => {} }
1112 match v { S{ a: 'a' } => {} }
1113 //^^^^^^^^^^^ Internal: match check bailed out
1114 match v { S{..} => {} }
1115 match v { _ => {} }
1116 match v { }
1117 //^ Missing match arm
1118}
1119"#,
1120 );
1121 }
1122
1123 #[test]
1124 fn binding() {
1125 check_diagnostics(
1126 r#"
1127fn main() {
1128 match true {
1129 _x @ true => {}
1130 false => {}
1131 }
1132 match true { _x @ true => {} }
1133 //^^^^ Missing match arm
1134}
1135"#,
1136 );
1137 }
1138
1139 #[test]
1140 fn binding_ref_has_correct_type() {
1141 // Asserts `PatKind::Binding(ref _x): bool`, not &bool.
1142 // If that's not true match checking will panic with "incompatible constructors"
1143 // FIXME: make facilities to test this directly like `tests::check_infer(..)`
1144 check_diagnostics(
1145 r#"
1146enum Foo { A }
1147fn main() {
1148 // FIXME: this should not bail out but current behavior is such as the old algorithm.
1149 // ExprValidator::validate_match(..) checks types of top level patterns incorrecly.
1150 match Foo::A {
1151 ref _x => {}
1152 // ^^^^^^ Internal: match check bailed out
1153 Foo::A => {}
1154 }
1155 match (true,) {
1156 (ref _x,) => {}
1157 (true,) => {}
1158 }
1159}
1160"#,
1161 );
1162 }
1163
1164 #[test]
1165 fn enum_non_exhaustive() {
1166 check_diagnostics(
1167 r#"
1168//- /lib.rs crate:lib
1169#[non_exhaustive]
1170pub enum E { A, B }
1171fn _local() {
1172 match E::A { _ => {} }
1173 match E::A {
1174 E::A => {}
1175 E::B => {}
1176 }
1177 match E::A {
1178 E::A | E::B => {}
1179 }
1180}
1181
1182//- /main.rs crate:main deps:lib
1183use lib::E;
1184fn main() {
1185 match E::A { _ => {} }
1186 match E::A {
1187 //^^^^ Missing match arm
1188 E::A => {}
1189 E::B => {}
1190 }
1191 match E::A {
1192 //^^^^ Missing match arm
1193 E::A | E::B => {}
1194 }
1195}
1196"#,
1197 );
1198 }
1199
1200 #[test]
1201 fn match_guard() {
1202 check_diagnostics(
1203 r#"
1204fn main() {
1205 match true {
1206 true if false => {}
1207 true => {}
1208 false => {}
1209 }
1210 match true {
1211 //^^^^ Missing match arm
1212 true if false => {}
1213 false => {}
1214}
1215"#,
1216 );
1217 }
1218
1219 #[test]
1220 fn pattern_type_is_of_substitution() {
1221 cov_mark::check!(match_check_wildcard_expanded_to_substitutions);
1222 check_diagnostics(
1223 r#"
1224struct Foo<T>(T);
1225struct Bar;
1226fn main() {
1227 match Foo(Bar) {
1228 _ | Foo(Bar) => {}
1229 }
1230}
1231"#,
1232 );
1233 }
1234
1235 #[test]
1236 fn record_struct_no_such_field() {
1237 check_diagnostics(
1238 r#"
1239struct Foo { }
1240fn main(f: Foo) {
1241 match f { Foo { bar } => () }
1242 // ^^^^^^^^^^^ Internal: match check bailed out
1243}
1244"#,
1245 );
1246 }
1247
1248 #[test]
1249 fn match_ergonomics_issue_9095() {
1250 check_diagnostics(
1251 r#"
1252enum Foo<T> { A(T) }
1253fn main() {
1254 match &Foo::A(true) {
1255 _ => {}
1256 Foo::A(_) => {}
1257 }
1258}
1259"#,
1260 );
1261 }
1262
1263 mod false_negatives {
1264 //! The implementation of match checking here is a work in progress. As we roll this out, we
1265 //! prefer false negatives to false positives (ideally there would be no false positives). This
1266 //! test module should document known false negatives. Eventually we will have a complete
1267 //! implementation of match checking and this module will be empty.
1268 //!
1269 //! The reasons for documenting known false negatives:
1270 //!
1271 //! 1. It acts as a backlog of work that can be done to improve the behavior of the system.
1272 //! 2. It ensures the code doesn't panic when handling these cases.
1273 use super::*;
1274
1275 #[test]
1276 fn integers() {
1277 // We don't currently check integer exhaustiveness.
1278 check_diagnostics(
1279 r#"
1280fn main() {
1281 match 5 {
1282 10 => (),
1283 // ^^ Internal: match check bailed out
1284 11..20 => (),
1285 }
1286}
1287"#,
1288 );
1289 }
1290
1291 #[test]
1292 fn reference_patterns_at_top_level() {
1293 check_diagnostics(
1294 r#"
1295fn main() {
1296 match &false {
1297 &true => {}
1298 // ^^^^^ Internal: match check bailed out
1299 }
1300}
1301 "#,
1302 );
1303 }
1304
1305 #[test]
1306 fn reference_patterns_in_fields() {
1307 check_diagnostics(
1308 r#"
1309fn main() {
1310 match (&false,) {
1311 (true,) => {}
1312 // ^^^^^^^ Internal: match check bailed out
1313 }
1314 match (&false,) {
1315 (&true,) => {}
1316 // ^^^^^^^^ Internal: match check bailed out
1317 }
1318}
1319 "#,
1320 );
1321 }
1322 }
1323}
diff --git a/crates/hir_ty/src/diagnostics/match_check/deconstruct_pat.rs b/crates/hir_ty/src/diagnostics/match_check/deconstruct_pat.rs
index 1f4219b42..222141bd6 100644
--- a/crates/hir_ty/src/diagnostics/match_check/deconstruct_pat.rs
+++ b/crates/hir_ty/src/diagnostics/match_check/deconstruct_pat.rs
@@ -664,7 +664,6 @@ impl Fields {
664 let is_non_exhaustive = 664 let is_non_exhaustive =
665 is_field_list_non_exhaustive(variant_id, cx) && !adt_is_local; 665 is_field_list_non_exhaustive(variant_id, cx) && !adt_is_local;
666 666
667 cov_mark::hit!(match_check_wildcard_expanded_to_substitutions);
668 let field_ty_data = cx.db.field_types(variant_id); 667 let field_ty_data = cx.db.field_types(variant_id);
669 let field_tys = || { 668 let field_tys = || {
670 field_ty_data 669 field_ty_data
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index b791747c2..dffb6fdc8 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -204,8 +204,9 @@ pub(crate) fn diagnostics(
204 ); 204 );
205 }); 205 });
206 206
207 let internal_diagnostics = cfg!(test);
207 match sema.to_module_def(file_id) { 208 match sema.to_module_def(file_id) {
208 Some(m) => m.diagnostics(db, &mut sink), 209 Some(m) => m.diagnostics(db, &mut sink, internal_diagnostics),
209 None => { 210 None => {
210 sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(&parse.tree().syntax()) }); 211 sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(&parse.tree().syntax()) });
211 } 212 }
@@ -942,4 +943,1305 @@ fn main() {
942"#, 943"#,
943 ); 944 );
944 } 945 }
946
947 // Register the required standard library types to make the tests work
948 fn add_filter_map_with_find_next_boilerplate(body: &str) -> String {
949 let prefix = r#"
950 //- /main.rs crate:main deps:core
951 use core::iter::Iterator;
952 use core::option::Option::{self, Some, None};
953 "#;
954 let suffix = r#"
955 //- /core/lib.rs crate:core
956 pub mod option {
957 pub enum Option<T> { Some(T), None }
958 }
959 pub mod iter {
960 pub trait Iterator {
961 type Item;
962 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
963 fn next(&mut self) -> Option<Self::Item>;
964 }
965 pub struct FilterMap {}
966 impl Iterator for FilterMap {
967 type Item = i32;
968 fn next(&mut self) -> i32 { 7 }
969 }
970 }
971 "#;
972 format!("{}{}{}", prefix, body, suffix)
973 }
974
975 #[test]
976 fn replace_filter_map_next_with_find_map2() {
977 check_diagnostics(&add_filter_map_with_find_next_boilerplate(
978 r#"
979 fn foo() {
980 let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
981 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..)
982 }
983 "#,
984 ));
985 }
986
987 #[test]
988 fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() {
989 check_diagnostics(&add_filter_map_with_find_next_boilerplate(
990 r#"
991 fn foo() {
992 let m = [1, 2, 3]
993 .iter()
994 .filter_map(|x| if *x == 2 { Some (4) } else { None })
995 .len();
996 }
997 "#,
998 ));
999 }
1000
1001 #[test]
1002 fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() {
1003 check_diagnostics(&add_filter_map_with_find_next_boilerplate(
1004 r#"
1005 fn foo() {
1006 let m = [1, 2, 3]
1007 .iter()
1008 .filter_map(|x| if *x == 2 { Some (4) } else { None })
1009 .map(|x| x + 2)
1010 .len();
1011 }
1012 "#,
1013 ));
1014 }
1015
1016 #[test]
1017 fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() {
1018 check_diagnostics(&add_filter_map_with_find_next_boilerplate(
1019 r#"
1020 fn foo() {
1021 let m = [1, 2, 3]
1022 .iter()
1023 .filter_map(|x| if *x == 2 { Some (4) } else { None });
1024 let n = m.next();
1025 }
1026 "#,
1027 ));
1028 }
1029
1030 #[test]
1031 fn missing_record_pat_field_diagnostic() {
1032 check_diagnostics(
1033 r#"
1034struct S { foo: i32, bar: () }
1035fn baz(s: S) {
1036 let S { foo: _ } = s;
1037 //^ Missing structure fields:
1038 //| - bar
1039}
1040"#,
1041 );
1042 }
1043
1044 #[test]
1045 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
1046 check_diagnostics(
1047 r"
1048struct S { foo: i32, bar: () }
1049fn baz(s: S) -> i32 {
1050 match s {
1051 S { foo, .. } => foo,
1052 }
1053}
1054",
1055 )
1056 }
1057
1058 #[test]
1059 fn missing_record_pat_field_box() {
1060 check_diagnostics(
1061 r"
1062struct S { s: Box<u32> }
1063fn x(a: S) {
1064 let S { box s } = a;
1065}
1066",
1067 )
1068 }
1069
1070 #[test]
1071 fn missing_record_pat_field_ref() {
1072 check_diagnostics(
1073 r"
1074struct S { s: u32 }
1075fn x(a: S) {
1076 let S { ref s } = a;
1077}
1078",
1079 )
1080 }
1081
1082 #[test]
1083 fn simple_free_fn_zero() {
1084 check_diagnostics(
1085 r#"
1086fn zero() {}
1087fn f() { zero(1); }
1088 //^^^^^^^ Expected 0 arguments, found 1
1089"#,
1090 );
1091
1092 check_diagnostics(
1093 r#"
1094fn zero() {}
1095fn f() { zero(); }
1096"#,
1097 );
1098 }
1099
1100 #[test]
1101 fn simple_free_fn_one() {
1102 check_diagnostics(
1103 r#"
1104fn one(arg: u8) {}
1105fn f() { one(); }
1106 //^^^^^ Expected 1 argument, found 0
1107"#,
1108 );
1109
1110 check_diagnostics(
1111 r#"
1112fn one(arg: u8) {}
1113fn f() { one(1); }
1114"#,
1115 );
1116 }
1117
1118 #[test]
1119 fn method_as_fn() {
1120 check_diagnostics(
1121 r#"
1122struct S;
1123impl S { fn method(&self) {} }
1124
1125fn f() {
1126 S::method();
1127} //^^^^^^^^^^^ Expected 1 argument, found 0
1128"#,
1129 );
1130
1131 check_diagnostics(
1132 r#"
1133struct S;
1134impl S { fn method(&self) {} }
1135
1136fn f() {
1137 S::method(&S);
1138 S.method();
1139}
1140"#,
1141 );
1142 }
1143
1144 #[test]
1145 fn method_with_arg() {
1146 check_diagnostics(
1147 r#"
1148struct S;
1149impl S { fn method(&self, arg: u8) {} }
1150
1151 fn f() {
1152 S.method();
1153 } //^^^^^^^^^^ Expected 1 argument, found 0
1154 "#,
1155 );
1156
1157 check_diagnostics(
1158 r#"
1159struct S;
1160impl S { fn method(&self, arg: u8) {} }
1161
1162fn f() {
1163 S::method(&S, 0);
1164 S.method(1);
1165}
1166"#,
1167 );
1168 }
1169
1170 #[test]
1171 fn method_unknown_receiver() {
1172 // note: this is incorrect code, so there might be errors on this in the
1173 // future, but we shouldn't emit an argument count diagnostic here
1174 check_diagnostics(
1175 r#"
1176trait Foo { fn method(&self, arg: usize) {} }
1177
1178fn f() {
1179 let x;
1180 x.method();
1181}
1182"#,
1183 );
1184 }
1185
1186 #[test]
1187 fn tuple_struct() {
1188 check_diagnostics(
1189 r#"
1190struct Tup(u8, u16);
1191fn f() {
1192 Tup(0);
1193} //^^^^^^ Expected 2 arguments, found 1
1194"#,
1195 )
1196 }
1197
1198 #[test]
1199 fn enum_variant() {
1200 check_diagnostics(
1201 r#"
1202enum En { Variant(u8, u16), }
1203fn f() {
1204 En::Variant(0);
1205} //^^^^^^^^^^^^^^ Expected 2 arguments, found 1
1206"#,
1207 )
1208 }
1209
1210 #[test]
1211 fn enum_variant_type_macro() {
1212 check_diagnostics(
1213 r#"
1214macro_rules! Type {
1215 () => { u32 };
1216}
1217enum Foo {
1218 Bar(Type![])
1219}
1220impl Foo {
1221 fn new() {
1222 Foo::Bar(0);
1223 Foo::Bar(0, 1);
1224 //^^^^^^^^^^^^^^ Expected 1 argument, found 2
1225 Foo::Bar();
1226 //^^^^^^^^^^ Expected 1 argument, found 0
1227 }
1228}
1229 "#,
1230 );
1231 }
1232
1233 #[test]
1234 fn varargs() {
1235 check_diagnostics(
1236 r#"
1237extern "C" {
1238 fn fixed(fixed: u8);
1239 fn varargs(fixed: u8, ...);
1240 fn varargs2(...);
1241}
1242
1243fn f() {
1244 unsafe {
1245 fixed(0);
1246 fixed(0, 1);
1247 //^^^^^^^^^^^ Expected 1 argument, found 2
1248 varargs(0);
1249 varargs(0, 1);
1250 varargs2();
1251 varargs2(0);
1252 varargs2(0, 1);
1253 }
1254}
1255 "#,
1256 )
1257 }
1258
1259 #[test]
1260 fn arg_count_lambda() {
1261 check_diagnostics(
1262 r#"
1263fn main() {
1264 let f = |()| ();
1265 f();
1266 //^^^ Expected 1 argument, found 0
1267 f(());
1268 f((), ());
1269 //^^^^^^^^^ Expected 1 argument, found 2
1270}
1271"#,
1272 )
1273 }
1274
1275 #[test]
1276 fn cfgd_out_call_arguments() {
1277 check_diagnostics(
1278 r#"
1279struct C(#[cfg(FALSE)] ());
1280impl C {
1281 fn new() -> Self {
1282 Self(
1283 #[cfg(FALSE)]
1284 (),
1285 )
1286 }
1287
1288 fn method(&self) {}
1289}
1290
1291fn main() {
1292 C::new().method(#[cfg(FALSE)] 0);
1293}
1294 "#,
1295 );
1296 }
1297
1298 #[test]
1299 fn cfgd_out_fn_params() {
1300 check_diagnostics(
1301 r#"
1302fn foo(#[cfg(NEVER)] x: ()) {}
1303
1304struct S;
1305
1306impl S {
1307 fn method(#[cfg(NEVER)] self) {}
1308 fn method2(#[cfg(NEVER)] self, arg: u8) {}
1309 fn method3(self, #[cfg(NEVER)] arg: u8) {}
1310}
1311
1312extern "C" {
1313 fn fixed(fixed: u8, #[cfg(NEVER)] ...);
1314 fn varargs(#[cfg(not(NEVER))] ...);
1315}
1316
1317fn main() {
1318 foo();
1319 S::method();
1320 S::method2(0);
1321 S::method3(S);
1322 S.method3();
1323 unsafe {
1324 fixed(0);
1325 varargs(1, 2, 3);
1326 }
1327}
1328 "#,
1329 )
1330 }
1331
1332 #[test]
1333 fn missing_semicolon() {
1334 check_diagnostics(
1335 r#"
1336 fn test() -> i32 { 123; }
1337 //^^^ Remove this semicolon
1338 "#,
1339 );
1340 }
1341}
1342
1343#[cfg(test)]
1344pub(super) mod match_check_tests {
1345 use crate::diagnostics::tests::check_diagnostics;
1346
1347 #[test]
1348 fn empty_tuple() {
1349 check_diagnostics(
1350 r#"
1351fn main() {
1352 match () { }
1353 //^^ Missing match arm
1354 match (()) { }
1355 //^^^^ Missing match arm
1356
1357 match () { _ => (), }
1358 match () { () => (), }
1359 match (()) { (()) => (), }
1360}
1361"#,
1362 );
1363 }
1364
1365 #[test]
1366 fn tuple_of_two_empty_tuple() {
1367 check_diagnostics(
1368 r#"
1369fn main() {
1370 match ((), ()) { }
1371 //^^^^^^^^ Missing match arm
1372
1373 match ((), ()) { ((), ()) => (), }
1374}
1375"#,
1376 );
1377 }
1378
1379 #[test]
1380 fn boolean() {
1381 check_diagnostics(
1382 r#"
1383fn test_main() {
1384 match false { }
1385 //^^^^^ Missing match arm
1386 match false { true => (), }
1387 //^^^^^ Missing match arm
1388 match (false, true) {}
1389 //^^^^^^^^^^^^^ Missing match arm
1390 match (false, true) { (true, true) => (), }
1391 //^^^^^^^^^^^^^ Missing match arm
1392 match (false, true) {
1393 //^^^^^^^^^^^^^ Missing match arm
1394 (false, true) => (),
1395 (false, false) => (),
1396 (true, false) => (),
1397 }
1398 match (false, true) { (true, _x) => (), }
1399 //^^^^^^^^^^^^^ Missing match arm
1400
1401 match false { true => (), false => (), }
1402 match (false, true) {
1403 (false, _) => (),
1404 (true, false) => (),
1405 (_, true) => (),
1406 }
1407 match (false, true) {
1408 (true, true) => (),
1409 (true, false) => (),
1410 (false, true) => (),
1411 (false, false) => (),
1412 }
1413 match (false, true) {
1414 (true, _x) => (),
1415 (false, true) => (),
1416 (false, false) => (),
1417 }
1418 match (false, true, false) {
1419 (false, ..) => (),
1420 (true, ..) => (),
1421 }
1422 match (false, true, false) {
1423 (.., false) => (),
1424 (.., true) => (),
1425 }
1426 match (false, true, false) { (..) => (), }
1427}
1428"#,
1429 );
1430 }
1431
1432 #[test]
1433 fn tuple_of_tuple_and_bools() {
1434 check_diagnostics(
1435 r#"
1436fn main() {
1437 match (false, ((), false)) {}
1438 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1439 match (false, ((), false)) { (true, ((), true)) => (), }
1440 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1441 match (false, ((), false)) { (true, _) => (), }
1442 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1443
1444 match (false, ((), false)) {
1445 (true, ((), true)) => (),
1446 (true, ((), false)) => (),
1447 (false, ((), true)) => (),
1448 (false, ((), false)) => (),
1449 }
1450 match (false, ((), false)) {
1451 (true, ((), true)) => (),
1452 (true, ((), false)) => (),
1453 (false, _) => (),
1454 }
1455}
1456"#,
1457 );
1458 }
1459
1460 #[test]
1461 fn enums() {
1462 check_diagnostics(
1463 r#"
1464enum Either { A, B, }
1465
1466fn main() {
1467 match Either::A { }
1468 //^^^^^^^^^ Missing match arm
1469 match Either::B { Either::A => (), }
1470 //^^^^^^^^^ Missing match arm
1471
1472 match &Either::B {
1473 //^^^^^^^^^^ Missing match arm
1474 Either::A => (),
1475 }
1476
1477 match Either::B {
1478 Either::A => (), Either::B => (),
1479 }
1480 match &Either::B {
1481 Either::A => (), Either::B => (),
1482 }
1483}
1484"#,
1485 );
1486 }
1487
1488 #[test]
1489 fn enum_containing_bool() {
1490 check_diagnostics(
1491 r#"
1492enum Either { A(bool), B }
1493
1494fn main() {
1495 match Either::B { }
1496 //^^^^^^^^^ Missing match arm
1497 match Either::B {
1498 //^^^^^^^^^ Missing match arm
1499 Either::A(true) => (), Either::B => ()
1500 }
1501
1502 match Either::B {
1503 Either::A(true) => (),
1504 Either::A(false) => (),
1505 Either::B => (),
1506 }
1507 match Either::B {
1508 Either::B => (),
1509 _ => (),
1510 }
1511 match Either::B {
1512 Either::A(_) => (),
1513 Either::B => (),
1514 }
1515
1516}
1517 "#,
1518 );
1519 }
1520
1521 #[test]
1522 fn enum_different_sizes() {
1523 check_diagnostics(
1524 r#"
1525enum Either { A(bool), B(bool, bool) }
1526
1527fn main() {
1528 match Either::A(false) {
1529 //^^^^^^^^^^^^^^^^ Missing match arm
1530 Either::A(_) => (),
1531 Either::B(false, _) => (),
1532 }
1533
1534 match Either::A(false) {
1535 Either::A(_) => (),
1536 Either::B(true, _) => (),
1537 Either::B(false, _) => (),
1538 }
1539 match Either::A(false) {
1540 Either::A(true) | Either::A(false) => (),
1541 Either::B(true, _) => (),
1542 Either::B(false, _) => (),
1543 }
1544}
1545"#,
1546 );
1547 }
1548
1549 #[test]
1550 fn tuple_of_enum_no_diagnostic() {
1551 check_diagnostics(
1552 r#"
1553enum Either { A(bool), B(bool, bool) }
1554enum Either2 { C, D }
1555
1556fn main() {
1557 match (Either::A(false), Either2::C) {
1558 (Either::A(true), _) | (Either::A(false), _) => (),
1559 (Either::B(true, _), Either2::C) => (),
1560 (Either::B(false, _), Either2::C) => (),
1561 (Either::B(_, _), Either2::D) => (),
1562 }
1563}
1564"#,
1565 );
1566 }
1567
1568 #[test]
1569 fn or_pattern_no_diagnostic() {
1570 check_diagnostics(
1571 r#"
1572enum Either {A, B}
1573
1574fn main() {
1575 match (Either::A, Either::B) {
1576 (Either::A | Either::B, _) => (),
1577 }
1578}"#,
1579 )
1580 }
1581
1582 #[test]
1583 fn mismatched_types() {
1584 // Match statements with arms that don't match the
1585 // expression pattern do not fire this diagnostic.
1586 check_diagnostics(
1587 r#"
1588enum Either { A, B }
1589enum Either2 { C, D }
1590
1591fn main() {
1592 match Either::A {
1593 Either2::C => (),
1594 // ^^^^^^^^^^ Internal: match check bailed out
1595 Either2::D => (),
1596 }
1597 match (true, false) {
1598 (true, false, true) => (),
1599 // ^^^^^^^^^^^^^^^^^^^ Internal: match check bailed out
1600 (true) => (),
1601 }
1602 match (true, false) { (true,) => {} }
1603 // ^^^^^^^ Internal: match check bailed out
1604 match (0) { () => () }
1605 // ^^ Internal: match check bailed out
1606 match Unresolved::Bar { Unresolved::Baz => () }
1607}
1608 "#,
1609 );
1610 }
1611
1612 #[test]
1613 fn mismatched_types_in_or_patterns() {
1614 check_diagnostics(
1615 r#"
1616fn main() {
1617 match false { true | () => {} }
1618 // ^^^^^^^^^ Internal: match check bailed out
1619 match (false,) { (true | (),) => {} }
1620 // ^^^^^^^^^^^^ Internal: match check bailed out
1621}
1622"#,
1623 );
1624 }
1625
1626 #[test]
1627 fn malformed_match_arm_tuple_enum_missing_pattern() {
1628 // We are testing to be sure we don't panic here when the match
1629 // arm `Either::B` is missing its pattern.
1630 check_diagnostics(
1631 r#"
1632enum Either { A, B(u32) }
1633
1634fn main() {
1635 match Either::A {
1636 Either::A => (),
1637 Either::B() => (),
1638 }
1639}
1640"#,
1641 );
1642 }
1643
1644 #[test]
1645 fn malformed_match_arm_extra_fields() {
1646 check_diagnostics(
1647 r#"
1648enum A { B(isize, isize), C }
1649fn main() {
1650 match A::B(1, 2) {
1651 A::B(_, _, _) => (),
1652 // ^^^^^^^^^^^^^ Internal: match check bailed out
1653 }
1654 match A::B(1, 2) {
1655 A::C(_) => (),
1656 // ^^^^^^^ Internal: match check bailed out
1657 }
1658}
1659"#,
1660 );
1661 }
1662
1663 #[test]
1664 fn expr_diverges() {
1665 check_diagnostics(
1666 r#"
1667enum Either { A, B }
1668
1669fn main() {
1670 match loop {} {
1671 Either::A => (),
1672 // ^^^^^^^^^ Internal: match check bailed out
1673 Either::B => (),
1674 }
1675 match loop {} {
1676 Either::A => (),
1677 // ^^^^^^^^^ Internal: match check bailed out
1678 }
1679 match loop { break Foo::A } {
1680 //^^^^^^^^^^^^^^^^^^^^^ Missing match arm
1681 Either::A => (),
1682 }
1683 match loop { break Foo::A } {
1684 Either::A => (),
1685 Either::B => (),
1686 }
1687}
1688"#,
1689 );
1690 }
1691
1692 #[test]
1693 fn expr_partially_diverges() {
1694 check_diagnostics(
1695 r#"
1696enum Either<T> { A(T), B }
1697
1698fn foo() -> Either<!> { Either::B }
1699fn main() -> u32 {
1700 match foo() {
1701 Either::A(val) => val,
1702 Either::B => 0,
1703 }
1704}
1705"#,
1706 );
1707 }
1708
1709 #[test]
1710 fn enum_record() {
1711 check_diagnostics(
1712 r#"
1713enum Either { A { foo: bool }, B }
1714
1715fn main() {
1716 let a = Either::A { foo: true };
1717 match a { }
1718 //^ Missing match arm
1719 match a { Either::A { foo: true } => () }
1720 //^ Missing match arm
1721 match a {
1722 Either::A { } => (),
1723 //^^^^^^^^^ Missing structure fields:
1724 // | - foo
1725 Either::B => (),
1726 }
1727 match a {
1728 //^ Missing match arm
1729 Either::A { } => (),
1730 } //^^^^^^^^^ Missing structure fields:
1731 // | - foo
1732
1733 match a {
1734 Either::A { foo: true } => (),
1735 Either::A { foo: false } => (),
1736 Either::B => (),
1737 }
1738 match a {
1739 Either::A { foo: _ } => (),
1740 Either::B => (),
1741 }
1742}
1743"#,
1744 );
1745 }
1746
1747 #[test]
1748 fn enum_record_fields_out_of_order() {
1749 check_diagnostics(
1750 r#"
1751enum Either {
1752 A { foo: bool, bar: () },
1753 B,
1754}
1755
1756fn main() {
1757 let a = Either::A { foo: true, bar: () };
1758 match a {
1759 //^ Missing match arm
1760 Either::A { bar: (), foo: false } => (),
1761 Either::A { foo: true, bar: () } => (),
1762 }
1763
1764 match a {
1765 Either::A { bar: (), foo: false } => (),
1766 Either::A { foo: true, bar: () } => (),
1767 Either::B => (),
1768 }
1769}
1770"#,
1771 );
1772 }
1773
1774 #[test]
1775 fn enum_record_ellipsis() {
1776 check_diagnostics(
1777 r#"
1778enum Either {
1779 A { foo: bool, bar: bool },
1780 B,
1781}
1782
1783fn main() {
1784 let a = Either::B;
1785 match a {
1786 //^ Missing match arm
1787 Either::A { foo: true, .. } => (),
1788 Either::B => (),
1789 }
1790 match a {
1791 //^ Missing match arm
1792 Either::A { .. } => (),
1793 }
1794
1795 match a {
1796 Either::A { foo: true, .. } => (),
1797 Either::A { foo: false, .. } => (),
1798 Either::B => (),
1799 }
1800
1801 match a {
1802 Either::A { .. } => (),
1803 Either::B => (),
1804 }
1805}
1806"#,
1807 );
1808 }
1809
1810 #[test]
1811 fn enum_tuple_partial_ellipsis() {
1812 check_diagnostics(
1813 r#"
1814enum Either {
1815 A(bool, bool, bool, bool),
1816 B,
1817}
1818
1819fn main() {
1820 match Either::B {
1821 //^^^^^^^^^ Missing match arm
1822 Either::A(true, .., true) => (),
1823 Either::A(true, .., false) => (),
1824 Either::A(false, .., false) => (),
1825 Either::B => (),
1826 }
1827 match Either::B {
1828 //^^^^^^^^^ Missing match arm
1829 Either::A(true, .., true) => (),
1830 Either::A(true, .., false) => (),
1831 Either::A(.., true) => (),
1832 Either::B => (),
1833 }
1834
1835 match Either::B {
1836 Either::A(true, .., true) => (),
1837 Either::A(true, .., false) => (),
1838 Either::A(false, .., true) => (),
1839 Either::A(false, .., false) => (),
1840 Either::B => (),
1841 }
1842 match Either::B {
1843 Either::A(true, .., true) => (),
1844 Either::A(true, .., false) => (),
1845 Either::A(.., true) => (),
1846 Either::A(.., false) => (),
1847 Either::B => (),
1848 }
1849}
1850"#,
1851 );
1852 }
1853
1854 #[test]
1855 fn never() {
1856 check_diagnostics(
1857 r#"
1858enum Never {}
1859
1860fn enum_(never: Never) {
1861 match never {}
1862}
1863fn enum_ref(never: &Never) {
1864 match never {}
1865 //^^^^^ Missing match arm
1866}
1867fn bang(never: !) {
1868 match never {}
1869}
1870"#,
1871 );
1872 }
1873
1874 #[test]
1875 fn unknown_type() {
1876 check_diagnostics(
1877 r#"
1878enum Option<T> { Some(T), None }
1879
1880fn main() {
1881 // `Never` is deliberately not defined so that it's an uninferred type.
1882 match Option::<Never>::None {
1883 None => (),
1884 Some(never) => match never {},
1885 // ^^^^^^^^^^^ Internal: match check bailed out
1886 }
1887 match Option::<Never>::None {
1888 //^^^^^^^^^^^^^^^^^^^^^ Missing match arm
1889 Option::Some(_never) => {},
1890 }
1891}
1892"#,
1893 );
1894 }
1895
1896 #[test]
1897 fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
1898 check_diagnostics(
1899 r#"
1900fn main() {
1901 match (false, true, false) {
1902 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1903 (false, ..) => (),
1904 }
1905}"#,
1906 );
1907 }
1908
1909 #[test]
1910 fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() {
1911 check_diagnostics(
1912 r#"
1913fn main() {
1914 match (false, true, false) {
1915 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1916 (.., false) => (),
1917 }
1918}"#,
1919 );
1920 }
1921
1922 #[test]
1923 fn tuple_of_bools_with_ellipsis_in_middle_missing_arm() {
1924 check_diagnostics(
1925 r#"
1926fn main() {
1927 match (false, true, false) {
1928 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1929 (true, .., false) => (),
1930 }
1931}"#,
1932 );
1933 }
1934
1935 #[test]
1936 fn record_struct() {
1937 check_diagnostics(
1938 r#"struct Foo { a: bool }
1939fn main(f: Foo) {
1940 match f {}
1941 //^ Missing match arm
1942 match f { Foo { a: true } => () }
1943 //^ Missing match arm
1944 match &f { Foo { a: true } => () }
1945 //^^ Missing match arm
1946 match f { Foo { a: _ } => () }
1947 match f {
1948 Foo { a: true } => (),
1949 Foo { a: false } => (),
1950 }
1951 match &f {
1952 Foo { a: true } => (),
1953 Foo { a: false } => (),
1954 }
1955}
1956"#,
1957 );
1958 }
1959
1960 #[test]
1961 fn tuple_struct() {
1962 check_diagnostics(
1963 r#"struct Foo(bool);
1964fn main(f: Foo) {
1965 match f {}
1966 //^ Missing match arm
1967 match f { Foo(true) => () }
1968 //^ Missing match arm
1969 match f {
1970 Foo(true) => (),
1971 Foo(false) => (),
1972 }
1973}
1974"#,
1975 );
1976 }
1977
1978 #[test]
1979 fn unit_struct() {
1980 check_diagnostics(
1981 r#"struct Foo;
1982fn main(f: Foo) {
1983 match f {}
1984 //^ Missing match arm
1985 match f { Foo => () }
1986}
1987"#,
1988 );
1989 }
1990
1991 #[test]
1992 fn record_struct_ellipsis() {
1993 check_diagnostics(
1994 r#"struct Foo { foo: bool, bar: bool }
1995fn main(f: Foo) {
1996 match f { Foo { foo: true, .. } => () }
1997 //^ Missing match arm
1998 match f {
1999 //^ Missing match arm
2000 Foo { foo: true, .. } => (),
2001 Foo { bar: false, .. } => ()
2002 }
2003 match f { Foo { .. } => () }
2004 match f {
2005 Foo { foo: true, .. } => (),
2006 Foo { foo: false, .. } => ()
2007 }
2008}
2009"#,
2010 );
2011 }
2012
2013 #[test]
2014 fn internal_or() {
2015 check_diagnostics(
2016 r#"
2017fn main() {
2018 enum Either { A(bool), B }
2019 match Either::B {
2020 //^^^^^^^^^ Missing match arm
2021 Either::A(true | false) => (),
2022 }
2023}
2024"#,
2025 );
2026 }
2027
2028 #[test]
2029 fn no_panic_at_unimplemented_subpattern_type() {
2030 check_diagnostics(
2031 r#"
2032struct S { a: char}
2033fn main(v: S) {
2034 match v { S{ a } => {} }
2035 match v { S{ a: _x } => {} }
2036 match v { S{ a: 'a' } => {} }
2037 //^^^^^^^^^^^ Internal: match check bailed out
2038 match v { S{..} => {} }
2039 match v { _ => {} }
2040 match v { }
2041 //^ Missing match arm
2042}
2043"#,
2044 );
2045 }
2046
2047 #[test]
2048 fn binding() {
2049 check_diagnostics(
2050 r#"
2051fn main() {
2052 match true {
2053 _x @ true => {}
2054 false => {}
2055 }
2056 match true { _x @ true => {} }
2057 //^^^^ Missing match arm
2058}
2059"#,
2060 );
2061 }
2062
2063 #[test]
2064 fn binding_ref_has_correct_type() {
2065 // Asserts `PatKind::Binding(ref _x): bool`, not &bool.
2066 // If that's not true match checking will panic with "incompatible constructors"
2067 // FIXME: make facilities to test this directly like `tests::check_infer(..)`
2068 check_diagnostics(
2069 r#"
2070enum Foo { A }
2071fn main() {
2072 // FIXME: this should not bail out but current behavior is such as the old algorithm.
2073 // ExprValidator::validate_match(..) checks types of top level patterns incorrecly.
2074 match Foo::A {
2075 ref _x => {}
2076 // ^^^^^^ Internal: match check bailed out
2077 Foo::A => {}
2078 }
2079 match (true,) {
2080 (ref _x,) => {}
2081 (true,) => {}
2082 }
2083}
2084"#,
2085 );
2086 }
2087
2088 #[test]
2089 fn enum_non_exhaustive() {
2090 check_diagnostics(
2091 r#"
2092//- /lib.rs crate:lib
2093#[non_exhaustive]
2094pub enum E { A, B }
2095fn _local() {
2096 match E::A { _ => {} }
2097 match E::A {
2098 E::A => {}
2099 E::B => {}
2100 }
2101 match E::A {
2102 E::A | E::B => {}
2103 }
2104}
2105
2106//- /main.rs crate:main deps:lib
2107use lib::E;
2108fn main() {
2109 match E::A { _ => {} }
2110 match E::A {
2111 //^^^^ Missing match arm
2112 E::A => {}
2113 E::B => {}
2114 }
2115 match E::A {
2116 //^^^^ Missing match arm
2117 E::A | E::B => {}
2118 }
2119}
2120"#,
2121 );
2122 }
2123
2124 #[test]
2125 fn match_guard() {
2126 check_diagnostics(
2127 r#"
2128fn main() {
2129 match true {
2130 true if false => {}
2131 true => {}
2132 false => {}
2133 }
2134 match true {
2135 //^^^^ Missing match arm
2136 true if false => {}
2137 false => {}
2138 }
2139}
2140"#,
2141 );
2142 }
2143
2144 #[test]
2145 fn pattern_type_is_of_substitution() {
2146 check_diagnostics(
2147 r#"
2148struct Foo<T>(T);
2149struct Bar;
2150fn main() {
2151 match Foo(Bar) {
2152 _ | Foo(Bar) => {}
2153 }
2154}
2155"#,
2156 );
2157 }
2158
2159 #[test]
2160 fn record_struct_no_such_field() {
2161 check_diagnostics(
2162 r#"
2163struct Foo { }
2164fn main(f: Foo) {
2165 match f { Foo { bar } => () }
2166 // ^^^^^^^^^^^ Internal: match check bailed out
2167}
2168"#,
2169 );
2170 }
2171
2172 #[test]
2173 fn match_ergonomics_issue_9095() {
2174 check_diagnostics(
2175 r#"
2176enum Foo<T> { A(T) }
2177fn main() {
2178 match &Foo::A(true) {
2179 _ => {}
2180 Foo::A(_) => {}
2181 }
2182}
2183"#,
2184 );
2185 }
2186
2187 mod false_negatives {
2188 //! The implementation of match checking here is a work in progress. As we roll this out, we
2189 //! prefer false negatives to false positives (ideally there would be no false positives). This
2190 //! test module should document known false negatives. Eventually we will have a complete
2191 //! implementation of match checking and this module will be empty.
2192 //!
2193 //! The reasons for documenting known false negatives:
2194 //!
2195 //! 1. It acts as a backlog of work that can be done to improve the behavior of the system.
2196 //! 2. It ensures the code doesn't panic when handling these cases.
2197 use super::*;
2198
2199 #[test]
2200 fn integers() {
2201 // We don't currently check integer exhaustiveness.
2202 check_diagnostics(
2203 r#"
2204fn main() {
2205 match 5 {
2206 10 => (),
2207 // ^^ Internal: match check bailed out
2208 11..20 => (),
2209 }
2210}
2211"#,
2212 );
2213 }
2214
2215 #[test]
2216 fn reference_patterns_at_top_level() {
2217 check_diagnostics(
2218 r#"
2219fn main() {
2220 match &false {
2221 &true => {}
2222 // ^^^^^ Internal: match check bailed out
2223 }
2224}
2225 "#,
2226 );
2227 }
2228
2229 #[test]
2230 fn reference_patterns_in_fields() {
2231 check_diagnostics(
2232 r#"
2233fn main() {
2234 match (&false,) {
2235 (true,) => {}
2236 // ^^^^^^^ Internal: match check bailed out
2237 }
2238 match (&false,) {
2239 (&true,) => {}
2240 // ^^^^^^^^ Internal: match check bailed out
2241 }
2242}
2243 "#,
2244 );
2245 }
2246 }
945} 2247}