Skip to content

Commit a8da107

Browse files
feat: add non-exhaustive-let diagnostic
1 parent fd8397c commit a8da107

File tree

5 files changed

+126
-5
lines changed

5 files changed

+126
-5
lines changed

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

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use hir_expand::name;
1212
use itertools::Itertools;
1313
use rustc_hash::FxHashSet;
1414
use rustc_pattern_analysis::usefulness::{compute_match_usefulness, ValidityConstraint};
15+
use tracing::debug;
1516
use triomphe::Arc;
1617
use typed_arena::Arena;
1718

@@ -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
RemoveTrailingReturn {
4853
return_expr: ExprId,
4954
},
@@ -68,7 +73,7 @@ struct ExprValidator {
6873
owner: DefWithBodyId,
6974
body: Arc<Body>,
7075
infer: Arc<InferenceResult>,
71-
pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
76+
diagnostics: Vec<BodyValidationDiagnostic>,
7277
}
7378

7479
impl ExprValidator {
@@ -105,6 +110,9 @@ impl ExprValidator {
105110
Expr::If { .. } => {
106111
self.check_for_unnecessary_else(id, expr, &body);
107112
}
113+
Expr::Block { .. } => {
114+
self.validate_block(db, expr);
115+
}
108116
_ => {}
109117
}
110118
}
@@ -230,7 +238,7 @@ impl ExprValidator {
230238
if !witnesses.is_empty() {
231239
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
232240
match_expr,
233-
uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, arms),
241+
uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, m_arms.is_empty()),
234242
});
235243
}
236244
}
@@ -303,6 +311,47 @@ impl ExprValidator {
303311
}
304312
}
305313
}
314+
315+
fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) {
316+
let Expr::Block { statements, .. } = expr else { return };
317+
let pattern_arena = Arena::new();
318+
let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db, &pattern_arena);
319+
for stmt in &**statements {
320+
let Statement::Let { pat, initializer, else_branch: None, .. } = stmt else { continue };
321+
let Some(initializer) = initializer else { continue };
322+
let init_ty = &self.infer[*initializer];
323+
324+
let mut have_errors = false;
325+
let match_arm = rustc_pattern_analysis::MatchArm {
326+
pat: self.lower_pattern(&cx, *pat, db, &mut have_errors),
327+
has_guard: false,
328+
arm_data: (),
329+
};
330+
if have_errors {
331+
continue;
332+
}
333+
334+
let report = match compute_match_usefulness(
335+
&cx,
336+
&[match_arm],
337+
init_ty.clone(),
338+
ValidityConstraint::ValidOnly,
339+
) {
340+
Ok(v) => v,
341+
Err(e) => {
342+
debug!(?e, "match usefulness error");
343+
continue;
344+
}
345+
};
346+
let witnesses = report.non_exhaustiveness_witnesses;
347+
if !witnesses.is_empty() {
348+
self.diagnostics.push(BodyValidationDiagnostic::NonExhaustiveLet {
349+
pat: *pat,
350+
uncovered_patterns: missing_match_arms(&cx, init_ty, witnesses, false),
351+
});
352+
}
353+
}
354+
}
306355
}
307356

308357
struct FilterMapNextChecker {
@@ -443,7 +492,7 @@ fn missing_match_arms<'p>(
443492
cx: &MatchCheckCtx<'p>,
444493
scrut_ty: &Ty,
445494
witnesses: Vec<WitnessPat<'p>>,
446-
arms: &[MatchArm],
495+
arms_is_empty: bool,
447496
) -> String {
448497
struct DisplayWitness<'a, 'p>(&'a WitnessPat<'p>, &'a MatchCheckCtx<'p>);
449498
impl fmt::Display for DisplayWitness<'_, '_> {
@@ -458,7 +507,7 @@ fn missing_match_arms<'p>(
458507
Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(),
459508
_ => false,
460509
};
461-
if arms.is_empty() && !non_empty_enum {
510+
if arms_is_empty && !non_empty_enum {
462511
format!("type `{}` is non-empty", scrut_ty.display(cx.db))
463512
} else {
464513
let pat_display = |witness| DisplayWitness(witness, cx);

crates/hir/src/diagnostics.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ diagnostics![
6464
MissingUnsafe,
6565
MovedOutOfRef,
6666
NeedMut,
67+
NonExhaustiveLet,
6768
NoSuchField,
6869
PrivateAssocItem,
6970
PrivateField,
@@ -280,6 +281,12 @@ pub struct MissingMatchArms {
280281
pub uncovered_patterns: String,
281282
}
282283

284+
#[derive(Debug)]
285+
pub struct NonExhaustiveLet {
286+
pub pat: InFile<AstPtr<ast::Pat>>,
287+
pub uncovered_patterns: String,
288+
}
289+
283290
#[derive(Debug)]
284291
pub struct TypeMismatch {
285292
pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
@@ -456,6 +463,22 @@ impl AnyDiagnostic {
456463
Err(SyntheticSyntax) => (),
457464
}
458465
}
466+
BodyValidationDiagnostic::NonExhaustiveLet { pat, uncovered_patterns } => {
467+
match source_map.pat_syntax(pat) {
468+
Ok(source_ptr) => {
469+
if let Some(ast_pat) = source_ptr.value.clone().cast::<ast::Pat>() {
470+
return Some(
471+
NonExhaustiveLet {
472+
pat: InFile::new(source_ptr.file_id, ast_pat),
473+
uncovered_patterns,
474+
}
475+
.into(),
476+
);
477+
}
478+
}
479+
Err(SyntheticSyntax) => {}
480+
}
481+
}
459482
BodyValidationDiagnostic::RemoveTrailingReturn { return_expr } => {
460483
if let Ok(source_ptr) = source_map.expr_syntax(return_expr) {
461484
// Filters out desugared return expressions (e.g. desugared try operators).

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ fn f() {
817817
//- minicore: option
818818
fn f(_: i32) {}
819819
fn main() {
820-
let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7));
820+
let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7)) else { return };
821821
//^^^^^ 💡 warn: variable does not need to be mutable
822822
f(x);
823823
}
Lines changed: 47 additions & 0 deletions
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

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 remove_trailing_return;
@@ -359,6 +360,7 @@ pub fn diagnostics(
359360
AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
360361
AnyDiagnostic::MovedOutOfRef(d) => handlers::moved_out_of_ref::moved_out_of_ref(&ctx, &d),
361362
AnyDiagnostic::NeedMut(d) => handlers::mutability_errors::need_mut(&ctx, &d),
363+
AnyDiagnostic::NonExhaustiveLet(d) => handlers::non_exhaustive_let::non_exhaustive_let(&ctx, &d),
362364
AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
363365
AnyDiagnostic::PrivateAssocItem(d) => handlers::private_assoc_item::private_assoc_item(&ctx, &d),
364366
AnyDiagnostic::PrivateField(d) => handlers::private_field::private_field(&ctx, &d),

0 commit comments

Comments
 (0)