Skip to content

Commit

Permalink
feat: add non-exhaustive-let diagnostic
Browse files Browse the repository at this point in the history
  • Loading branch information
rosefromthedead committed Jan 7, 2024
1 parent e53792b commit 29e3c0a
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 4 deletions.
44 changes: 41 additions & 3 deletions crates/hir-ty/src/diagnostics/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -44,6 +45,10 @@ pub enum BodyValidationDiagnostic {
match_expr: ExprId,
uncovered_patterns: String,
},
NonExhaustiveLet {
pat: PatId,
uncovered_patterns: String,
},
}

impl BodyValidationDiagnostic {
Expand All @@ -59,7 +64,7 @@ impl BodyValidationDiagnostic {
struct ExprValidator {
owner: DefWithBodyId,
infer: Arc<InferenceResult>,
pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
diagnostics: Vec<BodyValidationDiagnostic>,
}

impl ExprValidator {
Expand Down Expand Up @@ -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);
}
_ => {}
}
}
Expand Down Expand Up @@ -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),
});
}
}
Expand All @@ -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 {
Expand Down Expand Up @@ -375,7 +413,7 @@ fn missing_match_arms<'p>(
cx: &MatchCheckCtx<'_, 'p>,
scrut_ty: &Ty,
witnesses: Vec<DeconstructedPat<'p>>,
arms: &[MatchArm],
arms: &[match_check::MatchArm<'_>],
) -> String {
struct DisplayWitness<'a, 'p>(&'a DeconstructedPat<'p>, &'a MatchCheckCtx<'a, 'p>);
impl fmt::Display for DisplayWitness<'_, '_> {
Expand Down
7 changes: 7 additions & 0 deletions crates/hir/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ diagnostics![
MissingUnsafe,
MovedOutOfRef,
NeedMut,
NonExhaustiveLet,
NoSuchField,
PrivateAssocItem,
PrivateField,
Expand Down Expand Up @@ -263,6 +264,12 @@ pub struct MissingMatchArms {
pub uncovered_patterns: String,
}

#[derive(Debug)]
pub struct NonExhaustiveLet {
pub pat: InFile<AstPtr<ast::Pat>>,
pub uncovered_patterns: String,
}

#[derive(Debug)]
pub struct TypeMismatch {
pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
Expand Down
16 changes: 16 additions & 0 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<ast::Pat>() {
acc.push(
NonExhaustiveLet {
pat: InFile::new(source_ptr.file_id, ast_pat),
uncovered_patterns,
}
.into(),
);
}
}
Err(SyntheticSyntax) => {}
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/ide-diagnostics/src/handlers/mutability_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
35 changes: 35 additions & 0 deletions crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs
Original file line number Diff line number Diff line change
@@ -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
}
"#,
);
}
}
2 changes: 2 additions & 0 deletions crates/ide-diagnostics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down

0 comments on commit 29e3c0a

Please sign in to comment.