feat: `min-exhaustive-patterns
This commit is contained in:
parent
acc2c5d67a
commit
20971602d5
@ -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 @@
|
||||
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 @@ fn validate_match(
|
||||
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 @@ fn validate_match(
|
||||
}
|
||||
}
|
||||
|
||||
// [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 @@ fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) {
|
||||
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");
|
||||
|
@ -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 @@ pub(crate) fn compute_match_usefulness(
|
||||
}
|
||||
}
|
||||
|
||||
// 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 @@ fn is_exhaustive_patterns_feature_on(&self) -> bool {
|
||||
self.exhaustive_patterns
|
||||
}
|
||||
fn is_min_exhaustive_patterns_feature_on(&self) -> bool {
|
||||
self.min_exhaustive_patterns
|
||||
true
|
||||
}
|
||||
|
||||
fn ctor_arity(
|
||||
|
@ -1032,6 +1032,44 @@ fn exponential_match() {
|
||||
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::*;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user