feat: `min-exhaustive-patterns

This commit is contained in:
Shoyu Vanilla 2024-08-11 22:48:37 +09:00
parent acc2c5d67a
commit 20971602d5
4 changed files with 131 additions and 15 deletions

View File

@ -4,20 +4,25 @@
use std::fmt; use std::fmt;
use chalk_solve::rust_ir::AdtKind;
use either::Either; use either::Either;
use hir_def::lang_item::LangItem; use hir_def::{
use hir_def::{resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule}; lang_item::LangItem,
use hir_def::{ItemContainerId, Lookup}; resolver::{HasResolver, ValueNs},
AdtId, AssocItemId, DefWithBodyId, HasModule, ItemContainerId, Lookup,
};
use intern::sym; use intern::sym;
use itertools::Itertools; use itertools::Itertools;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use rustc_pattern_analysis::constructor::Constructor; use rustc_pattern_analysis::constructor::Constructor;
use syntax::{ast, AstNode}; use syntax::{
ast::{self, UnaryOp},
AstNode,
};
use tracing::debug; use tracing::debug;
use triomphe::Arc; use triomphe::Arc;
use typed_arena::Arena; use typed_arena::Arena;
use crate::Interner;
use crate::{ use crate::{
db::HirDatabase, db::HirDatabase,
diagnostics::match_check::{ diagnostics::match_check::{
@ -25,7 +30,7 @@ use crate::{
pat_analysis::{self, DeconstructedPat, MatchCheckCtx, WitnessPat}, pat_analysis::{self, DeconstructedPat, MatchCheckCtx, WitnessPat},
}, },
display::HirDisplay, display::HirDisplay,
InferenceResult, Ty, TyExt, Adjust, InferenceResult, Interner, Ty, TyExt, TyKind,
}; };
pub(crate) use hir_def::{ pub(crate) use hir_def::{
@ -236,7 +241,12 @@ impl ExprValidator {
return; 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, Ok(report) => report,
Err(()) => return, 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) { fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) {
let (Expr::Block { statements, .. } let (Expr::Block { statements, .. }
| Expr::Async { statements, .. } | Expr::Async { statements, .. }
@ -285,7 +334,7 @@ impl ExprValidator {
has_guard: false, has_guard: false,
arm_data: (), 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, Ok(v) => v,
Err(e) => { Err(e) => {
debug!(?e, "match usefulness error"); debug!(?e, "match usefulness error");

View File

@ -69,22 +69,20 @@ pub(crate) struct MatchCheckCtx<'db> {
body: DefWithBodyId, body: DefWithBodyId,
pub(crate) db: &'db dyn HirDatabase, pub(crate) db: &'db dyn HirDatabase,
exhaustive_patterns: bool, exhaustive_patterns: bool,
min_exhaustive_patterns: bool,
} }
impl<'db> MatchCheckCtx<'db> { impl<'db> MatchCheckCtx<'db> {
pub(crate) fn new(module: ModuleId, body: DefWithBodyId, db: &'db dyn HirDatabase) -> Self { pub(crate) fn new(module: ModuleId, body: DefWithBodyId, db: &'db dyn HirDatabase) -> Self {
let def_map = db.crate_def_map(module.krate()); let def_map = db.crate_def_map(module.krate());
let exhaustive_patterns = def_map.is_unstable_feature_enabled(&sym::exhaustive_patterns); let exhaustive_patterns = def_map.is_unstable_feature_enabled(&sym::exhaustive_patterns);
let min_exhaustive_patterns = Self { module, body, db, exhaustive_patterns }
def_map.is_unstable_feature_enabled(&sym::min_exhaustive_patterns);
Self { module, body, db, exhaustive_patterns, min_exhaustive_patterns }
} }
pub(crate) fn compute_match_usefulness( pub(crate) fn compute_match_usefulness(
&self, &self,
arms: &[MatchArm<'db>], arms: &[MatchArm<'db>],
scrut_ty: Ty, scrut_ty: Ty,
known_valid_scrutinee: Option<bool>,
) -> Result<UsefulnessReport<'db, Self>, ()> { ) -> Result<UsefulnessReport<'db, Self>, ()> {
if scrut_ty.contains_unknown() { if scrut_ty.contains_unknown() {
return Err(()); 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::from_bool(known_valid_scrutinee.unwrap_or(true));
let place_validity = PlaceValidity::MaybeInvalid;
// Measured to take ~100ms on modern hardware. // Measured to take ~100ms on modern hardware.
let complexity_limit = Some(500000); let complexity_limit = Some(500000);
compute_match_usefulness(self, arms, scrut_ty, place_validity, complexity_limit) compute_match_usefulness(self, arms, scrut_ty, place_validity, complexity_limit)
@ -328,7 +325,7 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
self.exhaustive_patterns self.exhaustive_patterns
} }
fn is_min_exhaustive_patterns_feature_on(&self) -> bool { fn is_min_exhaustive_patterns_feature_on(&self) -> bool {
self.min_exhaustive_patterns true
} }
fn ctor_arity( fn ctor_arity(

View File

@ -1032,6 +1032,44 @@ fn f() {
check_diagnostics_no_bails(&code); 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 { mod rust_unstable {
use super::*; use super::*;

View File

@ -80,6 +80,38 @@ fn main() {
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered //^^^^ 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
}
"#, "#,
); );
} }