Skip to content

Commit cb8733f

Browse files
feat: add non-exhaustive-let diagnostic
1 parent d2d653c commit cb8733f

File tree

6 files changed

+113
-4
lines changed

6 files changed

+113
-4
lines changed

crates/hir-ty/src/diagnostics/expr.rs

+40-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use std::fmt;
66

77
use either::Either;
8+
use hir_def::hir::Statement;
89
use hir_def::lang_item::LangItem;
910
use hir_def::{resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule};
1011
use hir_def::{ItemContainerId, Lookup};
@@ -44,6 +45,10 @@ pub enum BodyValidationDiagnostic {
4445
match_expr: ExprId,
4546
uncovered_patterns: String,
4647
},
48+
NonExhaustiveLet {
49+
pat: PatId,
50+
uncovered_patterns: String,
51+
},
4752
}
4853

4954
impl BodyValidationDiagnostic {
@@ -61,7 +66,7 @@ struct ExprValidator {
6166
owner: DefWithBodyId,
6267
body: Arc<Body>,
6368
infer: Arc<InferenceResult>,
64-
pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
69+
diagnostics: Vec<BodyValidationDiagnostic>,
6570
}
6671

6772
impl ExprValidator {
@@ -88,6 +93,9 @@ impl ExprValidator {
8893
Expr::Call { .. } | Expr::MethodCall { .. } => {
8994
self.validate_call(db, id, expr, &mut filter_map_next_checker);
9095
}
96+
Expr::Block { .. } => {
97+
self.validate_block(db, expr);
98+
}
9199
_ => {}
92100
}
93101
}
@@ -211,7 +219,7 @@ impl ExprValidator {
211219
if !witnesses.is_empty() {
212220
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
213221
match_expr,
214-
uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, arms),
222+
uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, &m_arms),
215223
});
216224
}
217225
}
@@ -231,6 +239,35 @@ impl ExprValidator {
231239
}
232240
pattern
233241
}
242+
243+
fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) {
244+
let Expr::Block { statements, .. } = expr else { return };
245+
let pattern_arena = Arena::new();
246+
let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db, &pattern_arena);
247+
for stmt in &**statements {
248+
let Statement::Let { pat, initializer, else_branch: None, .. } = stmt else { continue };
249+
let Some(initializer) = initializer else { continue };
250+
let init_ty = &self.infer[*initializer];
251+
252+
let mut have_errors = false;
253+
let match_arm = match_check::MatchArm {
254+
pat: self.lower_pattern(&cx, *pat, db, &mut have_errors),
255+
has_guard: false,
256+
};
257+
if have_errors {
258+
continue;
259+
}
260+
261+
let report = compute_match_usefulness(&cx, &[match_arm], init_ty);
262+
let witnesses = report.non_exhaustiveness_witnesses;
263+
if !witnesses.is_empty() {
264+
self.diagnostics.push(BodyValidationDiagnostic::NonExhaustiveLet {
265+
pat: *pat,
266+
uncovered_patterns: missing_match_arms(&cx, init_ty, witnesses, &[match_arm]),
267+
});
268+
}
269+
}
270+
}
234271
}
235272

236273
struct FilterMapNextChecker {
@@ -371,7 +408,7 @@ fn missing_match_arms<'p>(
371408
cx: &MatchCheckCtx<'_, 'p>,
372409
scrut_ty: &Ty,
373410
witnesses: Vec<DeconstructedPat<'p>>,
374-
arms: &[MatchArm],
411+
arms: &[match_check::MatchArm<'_>],
375412
) -> String {
376413
struct DisplayWitness<'a, 'p>(&'a DeconstructedPat<'p>, &'a MatchCheckCtx<'a, 'p>);
377414
impl fmt::Display for DisplayWitness<'_, '_> {

crates/hir/src/diagnostics.rs

+7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ diagnostics![
4949
MissingUnsafe,
5050
MovedOutOfRef,
5151
NeedMut,
52+
NonExhaustiveLet,
5253
NoSuchField,
5354
PrivateAssocItem,
5455
PrivateField,
@@ -263,6 +264,12 @@ pub struct MissingMatchArms {
263264
pub uncovered_patterns: String,
264265
}
265266

267+
#[derive(Debug)]
268+
pub struct NonExhaustiveLet {
269+
pub pat: InFile<AstPtr<ast::Pat>>,
270+
pub uncovered_patterns: String,
271+
}
272+
266273
#[derive(Debug)]
267274
pub struct TypeMismatch {
268275
pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,

crates/hir/src/lib.rs

+16
Original file line numberDiff line numberDiff line change
@@ -1994,6 +1994,22 @@ impl DefWithBody {
19941994
Err(SyntheticSyntax) => (),
19951995
}
19961996
}
1997+
BodyValidationDiagnostic::NonExhaustiveLet { pat, uncovered_patterns } => {
1998+
match source_map.pat_syntax(pat) {
1999+
Ok(source_ptr) => {
2000+
if let Some(ast_pat) = source_ptr.value.clone().cast::<ast::Pat>() {
2001+
acc.push(
2002+
NonExhaustiveLet {
2003+
pat: InFile::new(source_ptr.file_id, ast_pat),
2004+
uncovered_patterns,
2005+
}
2006+
.into(),
2007+
);
2008+
}
2009+
}
2010+
Err(SyntheticSyntax) => {}
2011+
}
2012+
}
19972013
}
19982014
}
19992015

crates/ide-diagnostics/src/handlers/mutability_errors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ fn f() {
815815
//- minicore: option
816816
fn f(_: i32) {}
817817
fn main() {
818-
let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7));
818+
let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7)) else { return };
819819
//^^^^^ 💡 warn: variable does not need to be mutable
820820
f(x);
821821
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
2+
3+
// Diagnostic: non-exhaustive-let
4+
//
5+
// This diagnostic is triggered if a `let` statement without an `else` branch has a non-exhaustive
6+
// pattern.
7+
pub(crate) fn non_exhaustive_let(
8+
ctx: &DiagnosticsContext<'_>,
9+
d: &hir::NonExhaustiveLet,
10+
) -> Diagnostic {
11+
Diagnostic::new_with_syntax_node_ptr(
12+
ctx,
13+
DiagnosticCode::RustcHardError("E0005"),
14+
format!("non-exhaustive pattern: {}", d.uncovered_patterns),
15+
d.pat.clone().map(Into::into),
16+
)
17+
}
18+
19+
#[cfg(test)]
20+
mod tests {
21+
use crate::tests::check_diagnostics;
22+
23+
#[test]
24+
fn option_nonexhaustive() {
25+
check_diagnostics(
26+
r#"
27+
//- minicore: option
28+
fn main() {
29+
let None = Some(5);
30+
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
31+
}
32+
"#,
33+
);
34+
}
35+
36+
#[test]
37+
fn option_exhaustive() {
38+
check_diagnostics(
39+
r#"
40+
//- minicore: option
41+
fn main() {
42+
let Some(_) | None = Some(5);
43+
}
44+
"#,
45+
);
46+
}
47+
}

crates/ide-diagnostics/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod handlers {
4141
pub(crate) mod moved_out_of_ref;
4242
pub(crate) mod mutability_errors;
4343
pub(crate) mod no_such_field;
44+
pub(crate) mod non_exhaustive_let;
4445
pub(crate) mod private_assoc_item;
4546
pub(crate) mod private_field;
4647
pub(crate) mod replace_filter_map_next_with_find_map;
@@ -360,6 +361,7 @@ pub fn diagnostics(
360361
AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
361362
AnyDiagnostic::MovedOutOfRef(d) => handlers::moved_out_of_ref::moved_out_of_ref(&ctx, &d),
362363
AnyDiagnostic::NeedMut(d) => handlers::mutability_errors::need_mut(&ctx, &d),
364+
AnyDiagnostic::NonExhaustiveLet(d) => handlers::non_exhaustive_let::non_exhaustive_let(&ctx, &d),
363365
AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
364366
AnyDiagnostic::PrivateAssocItem(d) => handlers::private_assoc_item::private_assoc_item(&ctx, &d),
365367
AnyDiagnostic::PrivateField(d) => handlers::private_field::private_field(&ctx, &d),

0 commit comments

Comments
 (0)