diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index afb80e1f4450..0c5d6399619a 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -12,6 +12,7 @@ use hir_expand::name;
use itertools::Itertools;
use rustc_hash::FxHashSet;
use rustc_pattern_analysis::usefulness::{compute_match_usefulness, ValidityConstraint};
+use tracing::debug;
use triomphe::Arc;
use typed_arena::Arena;
@@ -44,6 +45,10 @@ pub enum BodyValidationDiagnostic {
match_expr: ExprId,
uncovered_patterns: String,
},
+ NonExhaustiveLet {
+ pat: PatId,
+ uncovered_patterns: String,
+ },
RemoveTrailingReturn {
return_expr: ExprId,
},
@@ -68,7 +73,7 @@ struct ExprValidator {
owner: DefWithBodyId,
body: Arc
,
infer: Arc,
- pub(super) diagnostics: Vec,
+ diagnostics: Vec,
}
impl ExprValidator {
@@ -105,6 +110,9 @@ impl ExprValidator {
Expr::If { .. } => {
self.check_for_unnecessary_else(id, expr, &body);
}
+ Expr::Block { .. } => {
+ self.validate_block(db, expr);
+ }
_ => {}
}
}
@@ -231,11 +239,55 @@ impl ExprValidator {
if !witnesses.is_empty() {
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
match_expr,
- uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, arms),
+ uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, m_arms.is_empty()),
});
}
}
+ fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) {
+ let Expr::Block { statements, .. } = expr else { return };
+ let pattern_arena = Arena::new();
+ let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db);
+ for stmt in &**statements {
+ let &Statement::Let { pat, initializer, else_branch: None, .. } = stmt else {
+ continue;
+ };
+ let Some(initializer) = initializer else { continue };
+ let ty = &self.infer[initializer];
+
+ let mut have_errors = false;
+ let deconstructed_pat = self.lower_pattern(&cx, pat, db, &mut have_errors);
+ let match_arm = rustc_pattern_analysis::MatchArm {
+ pat: pattern_arena.alloc(deconstructed_pat),
+ has_guard: false,
+ arm_data: (),
+ };
+ if have_errors {
+ continue;
+ }
+
+ let report = match compute_match_usefulness(
+ &cx,
+ &[match_arm],
+ ty.clone(),
+ ValidityConstraint::ValidOnly,
+ ) {
+ Ok(v) => v,
+ Err(e) => {
+ debug!(?e, "match usefulness error");
+ continue;
+ }
+ };
+ let witnesses = report.non_exhaustiveness_witnesses;
+ if !witnesses.is_empty() {
+ self.diagnostics.push(BodyValidationDiagnostic::NonExhaustiveLet {
+ pat,
+ uncovered_patterns: missing_match_arms(&cx, ty, witnesses, false),
+ });
+ }
+ }
+ }
+
fn lower_pattern<'p>(
&self,
cx: &MatchCheckCtx<'p>,
@@ -444,7 +496,7 @@ fn missing_match_arms<'p>(
cx: &MatchCheckCtx<'p>,
scrut_ty: &Ty,
witnesses: Vec>,
- arms: &[MatchArm],
+ arms_is_empty: bool,
) -> String {
struct DisplayWitness<'a, 'p>(&'a WitnessPat<'p>, &'a MatchCheckCtx<'p>);
impl fmt::Display for DisplayWitness<'_, '_> {
@@ -459,7 +511,7 @@ fn missing_match_arms<'p>(
Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(),
_ => false,
};
- if arms.is_empty() && !non_empty_enum {
+ if arms_is_empty && !non_empty_enum {
format!("type `{}` is non-empty", scrut_ty.display(cx.db))
} else {
let pat_display = |witness| DisplayWitness(witness, cx);
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 08843a6c9994..d351e257d2e7 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -64,6 +64,7 @@ diagnostics![
MissingUnsafe,
MovedOutOfRef,
NeedMut,
+ NonExhaustiveLet,
NoSuchField,
PrivateAssocItem,
PrivateField,
@@ -280,6 +281,12 @@ pub struct MissingMatchArms {
pub uncovered_patterns: String,
}
+#[derive(Debug)]
+pub struct NonExhaustiveLet {
+ pub pat: InFile>,
+ pub uncovered_patterns: String,
+}
+
#[derive(Debug)]
pub struct TypeMismatch {
pub expr_or_pat: InFile>>,
@@ -456,6 +463,22 @@ impl AnyDiagnostic {
Err(SyntheticSyntax) => (),
}
}
+ BodyValidationDiagnostic::NonExhaustiveLet { pat, uncovered_patterns } => {
+ match source_map.pat_syntax(pat) {
+ Ok(source_ptr) => {
+ if let Some(ast_pat) = source_ptr.value.cast::() {
+ return Some(
+ NonExhaustiveLet {
+ pat: InFile::new(source_ptr.file_id, ast_pat),
+ uncovered_patterns,
+ }
+ .into(),
+ );
+ }
+ }
+ Err(SyntheticSyntax) => {}
+ }
+ }
BodyValidationDiagnostic::RemoveTrailingReturn { return_expr } => {
if let Ok(source_ptr) = source_map.expr_syntax(return_expr) {
// Filters out desugared return expressions (e.g. desugared try operators).
diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
index bdb55a9d98a2..3c71f84dc485 100644
--- a/crates/ide-diagnostics/src/handlers/mutability_errors.rs
+++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
@@ -817,7 +817,7 @@ fn f() {
//- minicore: option
fn f(_: i32) {}
fn main() {
- let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7));
+ let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7)) else { return };
//^^^^^ 💡 warn: variable does not need to be mutable
f(x);
}
diff --git a/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs b/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs
new file mode 100644
index 000000000000..1a4d2877ef25
--- /dev/null
+++ b/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs
@@ -0,0 +1,47 @@
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
+
+// Diagnostic: non-exhaustive-let
+//
+// This diagnostic is triggered if a `let` statement without an `else` branch has a non-exhaustive
+// pattern.
+pub(crate) fn non_exhaustive_let(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::NonExhaustiveLet,
+) -> Diagnostic {
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0005"),
+ format!("non-exhaustive pattern: {}", d.uncovered_patterns),
+ d.pat.map(Into::into),
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn option_nonexhaustive() {
+ check_diagnostics(
+ r#"
+//- minicore: option
+fn main() {
+ let None = Some(5);
+ //^^^^ error: non-exhaustive pattern: `Some(_)` not covered
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn option_exhaustive() {
+ check_diagnostics(
+ r#"
+//- minicore: option
+fn main() {
+ let Some(_) | None = Some(5);
+}
+"#,
+ );
+ }
+}
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 9d21bb4cd9fb..3a3888011d75 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -41,6 +41,7 @@ mod handlers {
pub(crate) mod moved_out_of_ref;
pub(crate) mod mutability_errors;
pub(crate) mod no_such_field;
+ pub(crate) mod non_exhaustive_let;
pub(crate) mod private_assoc_item;
pub(crate) mod private_field;
pub(crate) mod remove_trailing_return;
@@ -359,6 +360,7 @@ pub fn diagnostics(
AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
AnyDiagnostic::MovedOutOfRef(d) => handlers::moved_out_of_ref::moved_out_of_ref(&ctx, &d),
AnyDiagnostic::NeedMut(d) => handlers::mutability_errors::need_mut(&ctx, &d),
+ AnyDiagnostic::NonExhaustiveLet(d) => handlers::non_exhaustive_let::non_exhaustive_let(&ctx, &d),
AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
AnyDiagnostic::PrivateAssocItem(d) => handlers::private_assoc_item::private_assoc_item(&ctx, &d),
AnyDiagnostic::PrivateField(d) => handlers::private_field::private_field(&ctx, &d),