Skip to content

Commit 1f25210

Browse files
committed
feat: `min-exhaustive-patterns
1 parent 32a86cb commit 1f25210

File tree

4 files changed

+131
-15
lines changed

4 files changed

+131
-15
lines changed

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

+57-8
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, .. }
258307
| Expr::Async { statements, .. }
@@ -285,7 +334,7 @@ impl ExprValidator {
285334
has_guard: false,
286335
arm_data: (),
287336
};
288-
let report = match cx.compute_match_usefulness(&[match_arm], ty.clone()) {
337+
let report = match cx.compute_match_usefulness(&[match_arm], ty.clone(), None) {
289338
Ok(v) => v,
290339
Err(e) => {
291340
debug!(?e, "match usefulness error");

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

+4-7
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

+38
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

+32
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,38 @@ fn main() {
8080
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
8181
}
8282
}
83+
"#
84+
);
85+
}
86+
87+
#[test]
88+
fn min_exhaustive() {
89+
check_diagnostics(
90+
r#"
91+
//- minicore: result
92+
fn test(x: Result<i32, !>) {
93+
let Ok(_y) = x;
94+
}
95+
"#,
96+
);
97+
check_diagnostics(
98+
r#"
99+
//- minicore: result
100+
fn test(ptr: *const Result<i32, !>) {
101+
unsafe {
102+
let Ok(_x) = *ptr;
103+
//^^^^^^ error: non-exhaustive pattern: `Err(_)` not covered
104+
}
105+
}
106+
"#,
107+
);
108+
check_diagnostics(
109+
r#"
110+
//- minicore: result
111+
fn test(x: Result<i32, &'static !>) {
112+
let Ok(_y) = x;
113+
//^^^^^^ error: non-exhaustive pattern: `Err(_)` not covered
114+
}
83115
"#,
84116
);
85117
}

0 commit comments

Comments
 (0)