diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs index ab34dc88d87b..311fa8915fc1 100644 --- a/crates/hir-ty/src/diagnostics/expr.rs +++ b/crates/hir-ty/src/diagnostics/expr.rs @@ -5,6 +5,7 @@ use std::fmt; use either::Either; +use hir_def::hir::Statement; use hir_def::lang_item::LangItem; use hir_def::{resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule}; use hir_def::{ItemContainerId, Lookup}; @@ -44,6 +45,10 @@ pub enum BodyValidationDiagnostic { match_expr: ExprId, uncovered_patterns: String, }, + NonExhaustiveLet { + pat: PatId, + uncovered_patterns: String, + }, } impl BodyValidationDiagnostic { @@ -59,7 +64,7 @@ impl BodyValidationDiagnostic { struct ExprValidator { owner: DefWithBodyId, infer: Arc, - pub(super) diagnostics: Vec, + diagnostics: Vec, } impl ExprValidator { @@ -89,6 +94,9 @@ impl ExprValidator { Expr::Call { .. } | Expr::MethodCall { .. } => { self.validate_call(db, id, expr, &mut filter_map_next_checker); } + Expr::Block { .. } => { + self.validate_block(db, expr); + } _ => {} } } @@ -214,7 +222,7 @@ 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), }); } } @@ -235,6 +243,36 @@ impl ExprValidator { } pattern } + + fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) { + let Expr::Block { statements, .. } = expr else { return }; + let body = db.body(self.owner); + let pattern_arena = Arena::new(); + let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db, &pattern_arena); + for stmt in &**statements { + let Statement::Let { pat, initializer, else_branch: None, .. } = stmt else { continue }; + let Some(initializer) = initializer else { continue }; + let init_ty = &self.infer[*initializer]; + + let mut have_errors = false; + let match_arm = match_check::MatchArm { + pat: self.lower_pattern(&cx, *pat, db, &body, &mut have_errors), + has_guard: false, + }; + if have_errors { + continue; + } + + let report = compute_match_usefulness(&cx, &[match_arm], init_ty); + let witnesses = report.non_exhaustiveness_witnesses; + if !witnesses.is_empty() { + self.diagnostics.push(BodyValidationDiagnostic::NonExhaustiveLet { + pat: *pat, + uncovered_patterns: missing_match_arms(&cx, init_ty, witnesses, &[match_arm]), + }); + } + } + } } struct FilterMapNextChecker { @@ -375,7 +413,7 @@ fn missing_match_arms<'p>( cx: &MatchCheckCtx<'_, 'p>, scrut_ty: &Ty, witnesses: Vec>, - arms: &[MatchArm], + arms: &[match_check::MatchArm<'_>], ) -> String { struct DisplayWitness<'a, 'p>(&'a DeconstructedPat<'p>, &'a MatchCheckCtx<'a, 'p>); impl fmt::Display for DisplayWitness<'_, '_> { diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index bf29a53913d1..f504cc398364 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -49,6 +49,7 @@ diagnostics![ MissingUnsafe, MovedOutOfRef, NeedMut, + NonExhaustiveLet, NoSuchField, PrivateAssocItem, PrivateField, @@ -263,6 +264,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>>, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index fc6c2aeb3bea..d60346289930 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1971,6 +1971,22 @@ impl DefWithBody { Err(SyntheticSyntax) => (), } } + BodyValidationDiagnostic::NonExhaustiveLet { pat, uncovered_patterns } => { + match source_map.pat_syntax(pat) { + Ok(source_ptr) => { + if let Some(ast_pat) = source_ptr.value.clone().cast::() { + acc.push( + NonExhaustiveLet { + pat: InFile::new(source_ptr.file_id, ast_pat), + uncovered_patterns, + } + .into(), + ); + } + } + Err(SyntheticSyntax) => {} + } + } } } diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs index 1875111492c6..b42088fcd451 100644 --- a/crates/ide-diagnostics/src/handlers/mutability_errors.rs +++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs @@ -815,7 +815,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..004619d8b4d0 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs @@ -0,0 +1,35 @@ +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.clone().map(Into::into), + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn option() { + check_diagnostics( + r#" +//- minicore: option +fn main() { + let None = Some(5); + //^^^^ error: non-exhaustive pattern: `Some(_)` not covered +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index c7ad09e7ebdd..ba61a25d741d 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 replace_filter_map_next_with_find_map; @@ -360,6 +361,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),