Unnamed repository; edit this file 'description' to name the repository.
feat: `min-exhaustive-patterns
Shoyu Vanilla 2024-08-13
parent f3d9c9d · commit 5316ba9
-rw-r--r--crates/hir-ty/src/diagnostics/expr.rs65
-rw-r--r--crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs11
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_match_arms.rs38
-rw-r--r--crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs32
4 files changed, 131 insertions, 15 deletions
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index 06c9b2e0e5..6e5a7cce9c 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -4,20 +4,25 @@
use std::fmt;
+use chalk_solve::rust_ir::AdtKind;
use either::Either;
-use hir_def::lang_item::LangItem;
-use hir_def::{resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule};
-use hir_def::{ItemContainerId, Lookup};
+use hir_def::{
+ lang_item::LangItem,
+ resolver::{HasResolver, ValueNs},
+ AdtId, AssocItemId, DefWithBodyId, HasModule, ItemContainerId, Lookup,
+};
use intern::sym;
use itertools::Itertools;
use rustc_hash::FxHashSet;
use rustc_pattern_analysis::constructor::Constructor;
-use syntax::{ast, AstNode};
+use syntax::{
+ ast::{self, UnaryOp},
+ AstNode,
+};
use tracing::debug;
use triomphe::Arc;
use typed_arena::Arena;
-use crate::Interner;
use crate::{
db::HirDatabase,
diagnostics::match_check::{
@@ -25,7 +30,7 @@ use crate::{
pat_analysis::{self, DeconstructedPat, MatchCheckCtx, WitnessPat},
},
display::HirDisplay,
- InferenceResult, Ty, TyExt,
+ Adjust, InferenceResult, Interner, Ty, TyExt, TyKind,
};
pub(crate) use hir_def::{
@@ -236,7 +241,12 @@ impl ExprValidator {
return;
}
- let report = match cx.compute_match_usefulness(m_arms.as_slice(), scrut_ty.clone()) {
+ let known_valid_scrutinee = Some(self.is_known_valid_scrutinee(scrutinee_expr, db));
+ let report = match cx.compute_match_usefulness(
+ m_arms.as_slice(),
+ scrut_ty.clone(),
+ known_valid_scrutinee,
+ ) {
Ok(report) => report,
Err(()) => return,
};
@@ -253,6 +263,45 @@ impl ExprValidator {
}
}
+ // [rustc's `is_known_valid_scrutinee`](https://github.com/rust-lang/rust/blob/c9bd03cb724e13cca96ad320733046cbdb16fbbe/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L288)
+ //
+ // While the above function in rustc uses thir exprs, r-a doesn't have them.
+ // So, the logic here is getting same result as "hir lowering + match with lowered thir"
+ // with "hir only"
+ fn is_known_valid_scrutinee(&self, scrutinee_expr: ExprId, db: &dyn HirDatabase) -> bool {
+ if self
+ .infer
+ .expr_adjustments
+ .get(&scrutinee_expr)
+ .is_some_and(|adjusts| adjusts.iter().any(|a| matches!(a.kind, Adjust::Deref(..))))
+ {
+ return false;
+ }
+
+ match &self.body[scrutinee_expr] {
+ Expr::UnaryOp { op: UnaryOp::Deref, .. } => false,
+ Expr::Path(path) => {
+ let value_or_partial = self
+ .owner
+ .resolver(db.upcast())
+ .resolve_path_in_value_ns_fully(db.upcast(), path);
+ value_or_partial.map_or(true, |v| !matches!(v, ValueNs::StaticId(_)))
+ }
+ Expr::Field { expr, .. } => match self.infer.type_of_expr[*expr].kind(Interner) {
+ TyKind::Adt(adt, ..)
+ if db.adt_datum(self.owner.krate(db.upcast()), *adt).kind == AdtKind::Union =>
+ {
+ false
+ }
+ _ => self.is_known_valid_scrutinee(*expr, db),
+ },
+ Expr::Index { base, .. } => self.is_known_valid_scrutinee(*base, db),
+ Expr::Cast { expr, .. } => self.is_known_valid_scrutinee(*expr, db),
+ Expr::Missing => false,
+ _ => true,
+ }
+ }
+
fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) {
let (Expr::Block { statements, .. }
| Expr::Async { statements, .. }
@@ -285,7 +334,7 @@ impl ExprValidator {
has_guard: false,
arm_data: (),
};
- let report = match cx.compute_match_usefulness(&[match_arm], ty.clone()) {
+ let report = match cx.compute_match_usefulness(&[match_arm], ty.clone(), None) {
Ok(v) => v,
Err(e) => {
debug!(?e, "match usefulness error");
diff --git a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
index a12e201cf3..b57e290b48 100644
--- a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
+++ b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
@@ -69,22 +69,20 @@ pub(crate) struct MatchCheckCtx<'db> {
body: DefWithBodyId,
pub(crate) db: &'db dyn HirDatabase,
exhaustive_patterns: bool,
- min_exhaustive_patterns: bool,
}
impl<'db> MatchCheckCtx<'db> {
pub(crate) fn new(module: ModuleId, body: DefWithBodyId, db: &'db dyn HirDatabase) -> Self {
let def_map = db.crate_def_map(module.krate());
let exhaustive_patterns = def_map.is_unstable_feature_enabled(&sym::exhaustive_patterns);
- let min_exhaustive_patterns =
- def_map.is_unstable_feature_enabled(&sym::min_exhaustive_patterns);
- Self { module, body, db, exhaustive_patterns, min_exhaustive_patterns }
+ Self { module, body, db, exhaustive_patterns }
}
pub(crate) fn compute_match_usefulness(
&self,
arms: &[MatchArm<'db>],
scrut_ty: Ty,
+ known_valid_scrutinee: Option<bool>,
) -> Result<UsefulnessReport<'db, Self>, ()> {
if scrut_ty.contains_unknown() {
return Err(());
@@ -95,8 +93,7 @@ impl<'db> MatchCheckCtx<'db> {
}
}
- // FIXME: Determine place validity correctly. For now, err on the safe side.
- let place_validity = PlaceValidity::MaybeInvalid;
+ let place_validity = PlaceValidity::from_bool(known_valid_scrutinee.unwrap_or(true));
// Measured to take ~100ms on modern hardware.
let complexity_limit = Some(500000);
compute_match_usefulness(self, arms, scrut_ty, place_validity, complexity_limit)
@@ -328,7 +325,7 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
self.exhaustive_patterns
}
fn is_min_exhaustive_patterns_feature_on(&self) -> bool {
- self.min_exhaustive_patterns
+ true
}
fn ctor_arity(
diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index 97296278c3..f39738f2fb 100644
--- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
@@ -1032,6 +1032,44 @@ fn f() {
check_diagnostics_no_bails(&code);
}
+ #[test]
+ fn min_exhaustive() {
+ check_diagnostics(
+ r#"
+//- minicore: result
+fn test(x: Result<i32, !>) {
+ match x {
+ Ok(_y) => {}
+ }
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
+//- minicore: result
+fn test(ptr: *const Result<i32, !>) {
+ unsafe {
+ match *ptr {
+ //^^^^ error: missing match arm: `Err(!)` not covered
+ Ok(_x) => {}
+ }
+ }
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
+//- minicore: result
+fn test(x: Result<i32, &'static !>) {
+ match x {
+ //^ error: missing match arm: `Err(_)` not covered
+ Ok(_y) => {}
+ }
+}
+"#,
+ );
+ }
+
mod rust_unstable {
use super::*;
diff --git a/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs b/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs
index 5a6977c255..9aace992ae 100644
--- a/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs
+++ b/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs
@@ -80,6 +80,38 @@ fn main() {
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
}
}
+"#
+ );
+ }
+
+ #[test]
+ fn min_exhaustive() {
+ check_diagnostics(
+ r#"
+//- minicore: result
+fn test(x: Result<i32, !>) {
+ let Ok(_y) = x;
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
+//- minicore: result
+fn test(ptr: *const Result<i32, !>) {
+ unsafe {
+ let Ok(_x) = *ptr;
+ //^^^^^^ error: non-exhaustive pattern: `Err(_)` not covered
+ }
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
+//- minicore: result
+fn test(x: Result<i32, &'static !>) {
+ let Ok(_y) = x;
+ //^^^^^^ error: non-exhaustive pattern: `Err(_)` not covered
+}
"#,
);
}