Skip to content

Commit 29e3c0a

Browse files
feat: add non-exhaustive-let diagnostic
1 parent e53792b commit 29e3c0a

File tree

6 files changed

+102
-4
lines changed

6 files changed

+102
-4
lines changed

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

Lines changed: 41 additions & 3 deletions
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 {
@@ -59,7 +64,7 @@ impl BodyValidationDiagnostic {
5964
struct ExprValidator {
6065
owner: DefWithBodyId,
6166
infer: Arc<InferenceResult>,
62-
pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
67+
diagnostics: Vec<BodyValidationDiagnostic>,
6368
}
6469

6570
impl ExprValidator {
@@ -89,6 +94,9 @@ impl ExprValidator {
8994
Expr::Call { .. } | Expr::MethodCall { .. } => {
9095
self.validate_call(db, id, expr, &mut filter_map_next_checker);
9196
}
97+
Expr::Block { .. } => {
98+
self.validate_block(db, expr);
99+
}
92100
_ => {}
93101
}
94102
}
@@ -214,7 +222,7 @@ impl ExprValidator {
214222
if !witnesses.is_empty() {
215223
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
216224
match_expr,
217-
uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, arms),
225+
uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, &m_arms),
218226
});
219227
}
220228
}
@@ -235,6 +243,36 @@ impl ExprValidator {
235243
}
236244
pattern
237245
}
246+
247+
fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) {
248+
let Expr::Block { statements, .. } = expr else { return };
249+
let body = db.body(self.owner);
250+
let pattern_arena = Arena::new();
251+
let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db, &pattern_arena);
252+
for stmt in &**statements {
253+
let Statement::Let { pat, initializer, else_branch: None, .. } = stmt else { continue };
254+
let Some(initializer) = initializer else { continue };
255+
let init_ty = &self.infer[*initializer];
256+
257+
let mut have_errors = false;
258+
let match_arm = match_check::MatchArm {
259+
pat: self.lower_pattern(&cx, *pat, db, &body, &mut have_errors),
260+
has_guard: false,
261+
};
262+
if have_errors {
263+
continue;
264+
}
265+
266+
let report = compute_match_usefulness(&cx, &[match_arm], init_ty);
267+
let witnesses = report.non_exhaustiveness_witnesses;
268+
if !witnesses.is_empty() {
269+
self.diagnostics.push(BodyValidationDiagnostic::NonExhaustiveLet {
270+
pat: *pat,
271+
uncovered_patterns: missing_match_arms(&cx, init_ty, witnesses, &[match_arm]),
272+
});
273+
}
274+
}
275+
}
238276
}
239277

240278
struct FilterMapNextChecker {
@@ -375,7 +413,7 @@ fn missing_match_arms<'p>(
375413
cx: &MatchCheckCtx<'_, 'p>,
376414
scrut_ty: &Ty,
377415
witnesses: Vec<DeconstructedPat<'p>>,
378-
arms: &[MatchArm],
416+
arms: &[match_check::MatchArm<'_>],
379417
) -> String {
380418
struct DisplayWitness<'a, 'p>(&'a DeconstructedPat<'p>, &'a MatchCheckCtx<'a, 'p>);
381419
impl fmt::Display for DisplayWitness<'_, '_> {

crates/hir/src/diagnostics.rs

Lines changed: 7 additions & 0 deletions
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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,6 +1971,22 @@ impl DefWithBody {
19711971
Err(SyntheticSyntax) => (),
19721972
}
19731973
}
1974+
BodyValidationDiagnostic::NonExhaustiveLet { pat, uncovered_patterns } => {
1975+
match source_map.pat_syntax(pat) {
1976+
Ok(source_ptr) => {
1977+
if let Some(ast_pat) = source_ptr.value.clone().cast::<ast::Pat>() {
1978+
acc.push(
1979+
NonExhaustiveLet {
1980+
pat: InFile::new(source_ptr.file_id, ast_pat),
1981+
uncovered_patterns,
1982+
}
1983+
.into(),
1984+
);
1985+
}
1986+
}
1987+
Err(SyntheticSyntax) => {}
1988+
}
1989+
}
19741990
}
19751991
}
19761992

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

Lines changed: 1 addition & 1 deletion
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
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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() {
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+
}

crates/ide-diagnostics/src/lib.rs

Lines changed: 2 additions & 0 deletions
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)