Skip to content

Commit a2154da

Browse files
committed
feat: `min-exhaustive-patterns
1 parent 0daeb5c commit a2154da

File tree

4 files changed

+120
-15
lines changed

4 files changed

+120
-15
lines changed

crates/hir-ty/src/diagnostics/expr.rs

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,33 @@
44
55
use std::fmt;
66

7+
use chalk_solve::rust_ir::AdtKind;
78
use either::Either;
8-
use hir_def::lang_item::LangItem;
9-
use hir_def::{resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule};
10-
use hir_def::{ItemContainerId, Lookup};
9+
use hir_def::{
10+
lang_item::LangItem,
11+
resolver::{HasResolver, ValueNs},
12+
AdtId, AssocItemId, DefWithBodyId, HasModule, ItemContainerId, Lookup,
13+
};
1114
use intern::sym;
1215
use itertools::Itertools;
1316
use rustc_hash::FxHashSet;
1417
use rustc_pattern_analysis::constructor::Constructor;
15-
use syntax::{ast, AstNode};
18+
use syntax::{
19+
ast::{self, UnaryOp},
20+
AstNode,
21+
};
1622
use tracing::debug;
1723
use triomphe::Arc;
1824
use typed_arena::Arena;
1925

20-
use crate::Interner;
2126
use crate::{
2227
db::HirDatabase,
2328
diagnostics::match_check::{
2429
self,
2530
pat_analysis::{self, DeconstructedPat, MatchCheckCtx, WitnessPat},
2631
},
2732
display::HirDisplay,
28-
InferenceResult, Ty, TyExt,
33+
Adjust, InferenceResult, Interner, Ty, TyExt, TyKind,
2934
};
3035

3136
pub(crate) use hir_def::{
@@ -236,7 +241,12 @@ impl ExprValidator {
236241
return;
237242
}
238243

239-
let report = match cx.compute_match_usefulness(m_arms.as_slice(), scrut_ty.clone()) {
244+
let known_valid_scrutinee = Some(self.is_known_valid_scrutinee(scrutinee_expr, db));
245+
let report = match cx.compute_match_usefulness(
246+
m_arms.as_slice(),
247+
scrut_ty.clone(),
248+
known_valid_scrutinee,
249+
) {
240250
Ok(report) => report,
241251
Err(()) => return,
242252
};
@@ -253,6 +263,45 @@ impl ExprValidator {
253263
}
254264
}
255265

266+
// [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)
267+
//
268+
// While the above function in rustc uses thir exprs, r-a doesn't have them.
269+
// So, the logic here is getting same result as "hir lowering + match with lowered thir"
270+
// with "hir only"
271+
fn is_known_valid_scrutinee(&self, scrutinee_expr: ExprId, db: &dyn HirDatabase) -> bool {
272+
if self
273+
.infer
274+
.expr_adjustments
275+
.get(&scrutinee_expr)
276+
.is_some_and(|adjusts| adjusts.iter().any(|a| matches!(a.kind, Adjust::Deref(..))))
277+
{
278+
return false;
279+
}
280+
281+
match &self.body[scrutinee_expr] {
282+
Expr::UnaryOp { op: UnaryOp::Deref, .. } => false,
283+
Expr::Path(path) => {
284+
let value_or_partial = self
285+
.owner
286+
.resolver(db.upcast())
287+
.resolve_path_in_value_ns_fully(db.upcast(), path);
288+
value_or_partial.map_or(true, |v| !matches!(v, ValueNs::StaticId(_)))
289+
}
290+
Expr::Field { expr, .. } => match self.infer.type_of_expr[*expr].kind(Interner) {
291+
TyKind::Adt(adt, ..)
292+
if db.adt_datum(self.owner.krate(db.upcast()), *adt).kind == AdtKind::Union =>
293+
{
294+
false
295+
}
296+
_ => self.is_known_valid_scrutinee(*expr, db),
297+
},
298+
Expr::Index { base, .. } => self.is_known_valid_scrutinee(*base, db),
299+
Expr::Cast { expr, .. } => self.is_known_valid_scrutinee(*expr, db),
300+
Expr::Missing => false,
301+
_ => true,
302+
}
303+
}
304+
256305
fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) {
257306
let Expr::Block { statements, .. } = expr else { return };
258307
let pattern_arena = Arena::new();
@@ -280,7 +329,7 @@ impl ExprValidator {
280329
has_guard: false,
281330
arm_data: (),
282331
};
283-
let report = match cx.compute_match_usefulness(&[match_arm], ty.clone()) {
332+
let report = match cx.compute_match_usefulness(&[match_arm], ty.clone(), None) {
284333
Ok(v) => v,
285334
Err(e) => {
286335
debug!(?e, "match usefulness error");

crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,20 @@ pub(crate) struct MatchCheckCtx<'db> {
6969
body: DefWithBodyId,
7070
pub(crate) db: &'db dyn HirDatabase,
7171
exhaustive_patterns: bool,
72-
min_exhaustive_patterns: bool,
7372
}
7473

7574
impl<'db> MatchCheckCtx<'db> {
7675
pub(crate) fn new(module: ModuleId, body: DefWithBodyId, db: &'db dyn HirDatabase) -> Self {
7776
let def_map = db.crate_def_map(module.krate());
7877
let exhaustive_patterns = def_map.is_unstable_feature_enabled(&sym::exhaustive_patterns);
79-
let min_exhaustive_patterns =
80-
def_map.is_unstable_feature_enabled(&sym::min_exhaustive_patterns);
81-
Self { module, body, db, exhaustive_patterns, min_exhaustive_patterns }
78+
Self { module, body, db, exhaustive_patterns }
8279
}
8380

8481
pub(crate) fn compute_match_usefulness(
8582
&self,
8683
arms: &[MatchArm<'db>],
8784
scrut_ty: Ty,
85+
known_valid_scrutinee: Option<bool>,
8886
) -> Result<UsefulnessReport<'db, Self>, ()> {
8987
if scrut_ty.contains_unknown() {
9088
return Err(());
@@ -95,8 +93,7 @@ impl<'db> MatchCheckCtx<'db> {
9593
}
9694
}
9795

98-
// FIXME: Determine place validity correctly. For now, err on the safe side.
99-
let place_validity = PlaceValidity::MaybeInvalid;
96+
let place_validity = PlaceValidity::from_bool(known_valid_scrutinee.unwrap_or(true));
10097
// Measured to take ~100ms on modern hardware.
10198
let complexity_limit = Some(500000);
10299
compute_match_usefulness(self, arms, scrut_ty, place_validity, complexity_limit)
@@ -328,7 +325,7 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
328325
self.exhaustive_patterns
329326
}
330327
fn is_min_exhaustive_patterns_feature_on(&self) -> bool {
331-
self.min_exhaustive_patterns
328+
true
332329
}
333330

334331
fn ctor_arity(

crates/ide-diagnostics/src/handlers/missing_match_arms.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,44 @@ fn f() {
10321032
check_diagnostics_no_bails(&code);
10331033
}
10341034

1035+
#[test]
1036+
fn min_exhaustive() {
1037+
check_diagnostics(
1038+
r#"
1039+
//- minicore: result
1040+
fn test(x: Result<i32, !>) {
1041+
match x {
1042+
Ok(_y) => {}
1043+
}
1044+
}
1045+
"#,
1046+
);
1047+
check_diagnostics(
1048+
r#"
1049+
//- minicore: result
1050+
fn test(ptr: *const Result<i32, !>) {
1051+
unsafe {
1052+
match *ptr {
1053+
//^^^^ error: missing match arm: `Err(!)` not covered
1054+
Ok(_x) => {}
1055+
}
1056+
}
1057+
}
1058+
"#,
1059+
);
1060+
check_diagnostics(
1061+
r#"
1062+
//- minicore: result
1063+
fn test(x: Result<i32, &'static !>) {
1064+
match x {
1065+
//^ error: missing match arm: `Err(_)` not covered
1066+
Ok(_y) => {}
1067+
}
1068+
}
1069+
"#,
1070+
);
1071+
}
1072+
10351073
mod rust_unstable {
10361074
use super::*;
10371075

crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ fn main() {
4141
fn main() {
4242
let Some(_) | None = Some(5);
4343
}
44+
"#,
45+
);
46+
}
47+
48+
#[test]
49+
fn min_exhaustive() {
50+
check_diagnostics(
51+
r#"
52+
//- minicore: result
53+
fn test(x: Result<i32, !>) {
54+
let Ok(_y) = x;
55+
}
56+
"#,
57+
);
58+
check_diagnostics(
59+
r#"
60+
//- minicore: result
61+
fn test(x: Result<i32, &'static !>) {
62+
let Ok(_y) = x;
63+
//^^^^^^ error: non-exhaustive pattern: `Err(_)` not covered
64+
}
4465
"#,
4566
);
4667
}

0 commit comments

Comments
 (0)