Skip to content

Commit 06e02d5

Browse files
committed
Auto merge of rust-lang#118308 - Nadrieril:sound-exhaustive-patterns-take-3, r=compiler-errors
Don't warn an empty pattern unreachable if we're not sure the data is valid Exhaustiveness checking used to be naive about the possibility of a place containing invalid data. This could cause it to emit an "unreachable pattern" lint on an arm that was in fact reachable, as in rust-lang#117119. This PR fixes that. We now track whether a place that is matched on may hold invalid data. This also forced me to be extra precise about how exhaustiveness manages empty types. Note that this now errs in the opposite direction: the following arm is truly unreachable (because the binding causes a read of the value) but not linted as such. I'd rather not recommend writing a `match ... {}` that has the implicit side-effect of loading the value. [Never patterns](rust-lang#118155) will solve this cleanly. ```rust match union.value { _x => unreachable!(), } ``` I recommend reviewing commit by commit. I went all-in on the test suite because this went through a lot of iterations and I kept everything. The bit I'm least confident in is `is_known_valid_scrutinee` in `check_match.rs`. Fixes rust-lang#117119.
2 parents c722d51 + c3df51a commit 06e02d5

18 files changed

+2823
-865
lines changed

compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs

+30-10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use smallvec::SmallVec;
2+
13
use crate::ty::context::TyCtxt;
24
use crate::ty::{self, DefId, ParamEnv, Ty};
35

@@ -31,27 +33,31 @@ impl<'tcx> InhabitedPredicate<'tcx> {
3133
/// Returns true if the corresponding type is inhabited in the given
3234
/// `ParamEnv` and module
3335
pub fn apply(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, module_def_id: DefId) -> bool {
34-
let Ok(result) = self
35-
.apply_inner::<!>(tcx, param_env, &|id| Ok(tcx.is_descendant_of(module_def_id, id)));
36+
let Ok(result) = self.apply_inner::<!>(tcx, param_env, &mut Default::default(), &|id| {
37+
Ok(tcx.is_descendant_of(module_def_id, id))
38+
});
3639
result
3740
}
3841

3942
/// Same as `apply`, but returns `None` if self contains a module predicate
4043
pub fn apply_any_module(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Option<bool> {
41-
self.apply_inner(tcx, param_env, &|_| Err(())).ok()
44+
self.apply_inner(tcx, param_env, &mut Default::default(), &|_| Err(())).ok()
4245
}
4346

4447
/// Same as `apply`, but `NotInModule(_)` predicates yield `false`. That is,
4548
/// privately uninhabited types are considered always uninhabited.
4649
pub fn apply_ignore_module(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> bool {
47-
let Ok(result) = self.apply_inner::<!>(tcx, param_env, &|_| Ok(true));
50+
let Ok(result) =
51+
self.apply_inner::<!>(tcx, param_env, &mut Default::default(), &|_| Ok(true));
4852
result
4953
}
5054

51-
fn apply_inner<E>(
55+
#[instrument(level = "debug", skip(tcx, param_env, in_module), ret)]
56+
fn apply_inner<E: std::fmt::Debug>(
5257
self,
5358
tcx: TyCtxt<'tcx>,
5459
param_env: ParamEnv<'tcx>,
60+
eval_stack: &mut SmallVec<[Ty<'tcx>; 1]>, // for cycle detection
5561
in_module: &impl Fn(DefId) -> Result<bool, E>,
5662
) -> Result<bool, E> {
5763
match self {
@@ -71,11 +77,25 @@ impl<'tcx> InhabitedPredicate<'tcx> {
7177
match normalized_pred {
7278
// We don't have more information than we started with, so consider inhabited.
7379
Self::GenericType(_) => Ok(true),
74-
pred => pred.apply_inner(tcx, param_env, in_module),
80+
pred => {
81+
// A type which is cyclic when monomorphized can happen here since the
82+
// layout error would only trigger later. See e.g. `tests/ui/sized/recursive-type-2.rs`.
83+
if eval_stack.contains(&t) {
84+
return Ok(true); // Recover; this will error later.
85+
}
86+
eval_stack.push(t);
87+
let ret = pred.apply_inner(tcx, param_env, eval_stack, in_module);
88+
eval_stack.pop();
89+
ret
90+
}
7591
}
7692
}
77-
Self::And([a, b]) => try_and(a, b, |x| x.apply_inner(tcx, param_env, in_module)),
78-
Self::Or([a, b]) => try_or(a, b, |x| x.apply_inner(tcx, param_env, in_module)),
93+
Self::And([a, b]) => {
94+
try_and(a, b, |x| x.apply_inner(tcx, param_env, eval_stack, in_module))
95+
}
96+
Self::Or([a, b]) => {
97+
try_or(a, b, |x| x.apply_inner(tcx, param_env, eval_stack, in_module))
98+
}
7999
}
80100
}
81101

@@ -197,7 +217,7 @@ impl<'tcx> InhabitedPredicate<'tcx> {
197217

198218
// this is basically like `f(a)? && f(b)?` but different in the case of
199219
// `Ok(false) && Err(_) -> Ok(false)`
200-
fn try_and<T, E>(a: T, b: T, f: impl Fn(T) -> Result<bool, E>) -> Result<bool, E> {
220+
fn try_and<T, E>(a: T, b: T, mut f: impl FnMut(T) -> Result<bool, E>) -> Result<bool, E> {
201221
let a = f(a);
202222
if matches!(a, Ok(false)) {
203223
return Ok(false);
@@ -209,7 +229,7 @@ fn try_and<T, E>(a: T, b: T, f: impl Fn(T) -> Result<bool, E>) -> Result<bool, E
209229
}
210230
}
211231

212-
fn try_or<T, E>(a: T, b: T, f: impl Fn(T) -> Result<bool, E>) -> Result<bool, E> {
232+
fn try_or<T, E>(a: T, b: T, mut f: impl FnMut(T) -> Result<bool, E>) -> Result<bool, E> {
213233
let a = f(a);
214234
if matches!(a, Ok(true)) {
215235
return Ok(true);

compiler/rustc_middle/src/ty/inhabitedness/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ impl<'tcx> VariantDef {
103103
}
104104

105105
impl<'tcx> Ty<'tcx> {
106+
#[instrument(level = "debug", skip(tcx), ret)]
106107
pub fn inhabited_predicate(self, tcx: TyCtxt<'tcx>) -> InhabitedPredicate<'tcx> {
107108
match self.kind() {
108109
// For now, unions are always considered inhabited

compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+119-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::deconstruct_pat::{Constructor, DeconstructedPat, WitnessPat};
22
use super::usefulness::{
3-
compute_match_usefulness, MatchArm, MatchCheckCtxt, Reachability, UsefulnessReport,
3+
compute_match_usefulness, MatchArm, MatchCheckCtxt, Usefulness, UsefulnessReport,
44
};
55

66
use crate::errors::*;
@@ -42,7 +42,7 @@ pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), Err
4242

4343
for param in thir.params.iter() {
4444
if let Some(box ref pattern) = param.pat {
45-
visitor.check_binding_is_irrefutable(pattern, "function argument", None);
45+
visitor.check_binding_is_irrefutable(pattern, "function argument", None, None);
4646
}
4747
}
4848
visitor.error
@@ -254,10 +254,11 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
254254
self.with_lint_level(lint_level, |this| this.visit_land_rhs(&this.thir[value]))
255255
}
256256
ExprKind::Let { box ref pat, expr } => {
257+
let expr = &self.thir()[expr];
257258
self.with_let_source(LetSource::None, |this| {
258-
this.visit_expr(&this.thir()[expr]);
259+
this.visit_expr(expr);
259260
});
260-
Ok(Some((ex.span, self.is_let_irrefutable(pat)?)))
261+
Ok(Some((ex.span, self.is_let_irrefutable(pat, Some(expr))?)))
261262
}
262263
_ => {
263264
self.with_let_source(LetSource::None, |this| {
@@ -287,35 +288,114 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
287288
}
288289
}
289290

291+
/// Inspects the match scrutinee expression to determine whether the place it evaluates to may
292+
/// hold invalid data.
293+
fn is_known_valid_scrutinee(&self, scrutinee: &Expr<'tcx>) -> bool {
294+
use ExprKind::*;
295+
match &scrutinee.kind {
296+
// Both pointers and references can validly point to a place with invalid data.
297+
Deref { .. } => false,
298+
// Inherit validity of the parent place, unless the parent is an union.
299+
Field { lhs, .. } => {
300+
let lhs = &self.thir()[*lhs];
301+
match lhs.ty.kind() {
302+
ty::Adt(def, _) if def.is_union() => false,
303+
_ => self.is_known_valid_scrutinee(lhs),
304+
}
305+
}
306+
// Essentially a field access.
307+
Index { lhs, .. } => {
308+
let lhs = &self.thir()[*lhs];
309+
self.is_known_valid_scrutinee(lhs)
310+
}
311+
312+
// No-op.
313+
Scope { value, .. } => self.is_known_valid_scrutinee(&self.thir()[*value]),
314+
315+
// Casts don't cause a load.
316+
NeverToAny { source }
317+
| Cast { source }
318+
| Use { source }
319+
| PointerCoercion { source, .. }
320+
| PlaceTypeAscription { source, .. }
321+
| ValueTypeAscription { source, .. } => {
322+
self.is_known_valid_scrutinee(&self.thir()[*source])
323+
}
324+
325+
// These diverge.
326+
Become { .. } | Break { .. } | Continue { .. } | Return { .. } => true,
327+
328+
// These are statements that evaluate to `()`.
329+
Assign { .. } | AssignOp { .. } | InlineAsm { .. } | Let { .. } => true,
330+
331+
// These evaluate to a value.
332+
AddressOf { .. }
333+
| Adt { .. }
334+
| Array { .. }
335+
| Binary { .. }
336+
| Block { .. }
337+
| Borrow { .. }
338+
| Box { .. }
339+
| Call { .. }
340+
| Closure { .. }
341+
| ConstBlock { .. }
342+
| ConstParam { .. }
343+
| If { .. }
344+
| Literal { .. }
345+
| LogicalOp { .. }
346+
| Loop { .. }
347+
| Match { .. }
348+
| NamedConst { .. }
349+
| NonHirLiteral { .. }
350+
| OffsetOf { .. }
351+
| Repeat { .. }
352+
| StaticRef { .. }
353+
| ThreadLocalRef { .. }
354+
| Tuple { .. }
355+
| Unary { .. }
356+
| UpvarRef { .. }
357+
| VarRef { .. }
358+
| ZstLiteral { .. }
359+
| Yield { .. } => true,
360+
}
361+
}
362+
290363
fn new_cx(
291364
&self,
292365
refutability: RefutableFlag,
293-
match_span: Option<Span>,
366+
whole_match_span: Option<Span>,
367+
scrutinee: Option<&Expr<'tcx>>,
294368
scrut_span: Span,
295369
) -> MatchCheckCtxt<'p, 'tcx> {
296370
let refutable = match refutability {
297371
Irrefutable => false,
298372
Refutable => true,
299373
};
374+
// If we don't have a scrutinee we're either a function parameter or a `let x;`. Both cases
375+
// require validity.
376+
let known_valid_scrutinee =
377+
scrutinee.map(|scrut| self.is_known_valid_scrutinee(scrut)).unwrap_or(true);
300378
MatchCheckCtxt {
301379
tcx: self.tcx,
302380
param_env: self.param_env,
303381
module: self.tcx.parent_module(self.lint_level).to_def_id(),
304382
pattern_arena: self.pattern_arena,
305383
match_lint_level: self.lint_level,
306-
match_span,
384+
whole_match_span,
307385
scrut_span,
308386
refutable,
387+
known_valid_scrutinee,
309388
}
310389
}
311390

312391
#[instrument(level = "trace", skip(self))]
313392
fn check_let(&mut self, pat: &Pat<'tcx>, scrutinee: Option<ExprId>, span: Span) {
314393
assert!(self.let_source != LetSource::None);
394+
let scrut = scrutinee.map(|id| &self.thir[id]);
315395
if let LetSource::PlainLet = self.let_source {
316-
self.check_binding_is_irrefutable(pat, "local binding", Some(span))
396+
self.check_binding_is_irrefutable(pat, "local binding", scrut, Some(span))
317397
} else {
318-
let Ok(refutability) = self.is_let_irrefutable(pat) else { return };
398+
let Ok(refutability) = self.is_let_irrefutable(pat, scrut) else { return };
319399
if matches!(refutability, Irrefutable) {
320400
report_irrefutable_let_patterns(
321401
self.tcx,
@@ -336,7 +416,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
336416
expr_span: Span,
337417
) {
338418
let scrut = &self.thir[scrut];
339-
let cx = self.new_cx(Refutable, Some(expr_span), scrut.span);
419+
let cx = self.new_cx(Refutable, Some(expr_span), Some(scrut), scrut.span);
340420

341421
let mut tarms = Vec::with_capacity(arms.len());
342422
for &arm in arms {
@@ -377,7 +457,12 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
377457
debug_assert_eq!(pat.span.desugaring_kind(), Some(DesugaringKind::ForLoop));
378458
let PatKind::Variant { ref subpatterns, .. } = pat.kind else { bug!() };
379459
let [pat_field] = &subpatterns[..] else { bug!() };
380-
self.check_binding_is_irrefutable(&pat_field.pattern, "`for` loop binding", None);
460+
self.check_binding_is_irrefutable(
461+
&pat_field.pattern,
462+
"`for` loop binding",
463+
None,
464+
None,
465+
);
381466
} else {
382467
self.error = Err(report_non_exhaustive_match(
383468
&cx, self.thir, scrut_ty, scrut.span, witnesses, arms, expr_span,
@@ -457,16 +542,21 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
457542
&mut self,
458543
pat: &Pat<'tcx>,
459544
refutability: RefutableFlag,
545+
scrut: Option<&Expr<'tcx>>,
460546
) -> Result<(MatchCheckCtxt<'p, 'tcx>, UsefulnessReport<'p, 'tcx>), ErrorGuaranteed> {
461-
let cx = self.new_cx(refutability, None, pat.span);
547+
let cx = self.new_cx(refutability, None, scrut, pat.span);
462548
let pat = self.lower_pattern(&cx, pat)?;
463549
let arms = [MatchArm { pat, hir_id: self.lint_level, has_guard: false }];
464550
let report = compute_match_usefulness(&cx, &arms, pat.ty());
465551
Ok((cx, report))
466552
}
467553

468-
fn is_let_irrefutable(&mut self, pat: &Pat<'tcx>) -> Result<RefutableFlag, ErrorGuaranteed> {
469-
let (cx, report) = self.analyze_binding(pat, Refutable)?;
554+
fn is_let_irrefutable(
555+
&mut self,
556+
pat: &Pat<'tcx>,
557+
scrut: Option<&Expr<'tcx>>,
558+
) -> Result<RefutableFlag, ErrorGuaranteed> {
559+
let (cx, report) = self.analyze_binding(pat, Refutable, scrut)?;
470560
// Report if the pattern is unreachable, which can only occur when the type is uninhabited.
471561
// This also reports unreachable sub-patterns.
472562
report_arm_reachability(&cx, &report);
@@ -476,10 +566,16 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
476566
}
477567

478568
#[instrument(level = "trace", skip(self))]
479-
fn check_binding_is_irrefutable(&mut self, pat: &Pat<'tcx>, origin: &str, sp: Option<Span>) {
569+
fn check_binding_is_irrefutable(
570+
&mut self,
571+
pat: &Pat<'tcx>,
572+
origin: &str,
573+
scrut: Option<&Expr<'tcx>>,
574+
sp: Option<Span>,
575+
) {
480576
let pattern_ty = pat.ty;
481577

482-
let Ok((cx, report)) = self.analyze_binding(pat, Irrefutable) else { return };
578+
let Ok((cx, report)) = self.analyze_binding(pat, Irrefutable, scrut) else { return };
483579
let witnesses = report.non_exhaustiveness_witnesses;
484580
if witnesses.is_empty() {
485581
// The pattern is irrefutable.
@@ -749,18 +845,18 @@ fn report_arm_reachability<'p, 'tcx>(
749845
);
750846
};
751847

752-
use Reachability::*;
848+
use Usefulness::*;
753849
let mut catchall = None;
754850
for (arm, is_useful) in report.arm_usefulness.iter() {
755851
match is_useful {
756-
Unreachable => report_unreachable_pattern(arm.pat.span(), arm.hir_id, catchall),
757-
Reachable(unreachables) if unreachables.is_empty() => {}
758-
// The arm is reachable, but contains unreachable subpatterns (from or-patterns).
759-
Reachable(unreachables) => {
760-
let mut unreachables = unreachables.clone();
852+
Redundant => report_unreachable_pattern(arm.pat.span(), arm.hir_id, catchall),
853+
Useful(redundant_spans) if redundant_spans.is_empty() => {}
854+
// The arm is reachable, but contains redundant subpatterns (from or-patterns).
855+
Useful(redundant_spans) => {
856+
let mut redundant_spans = redundant_spans.clone();
761857
// Emit lints in the order in which they occur in the file.
762-
unreachables.sort_unstable();
763-
for span in unreachables {
858+
redundant_spans.sort_unstable();
859+
for span in redundant_spans {
764860
report_unreachable_pattern(span, arm.hir_id, None);
765861
}
766862
}

0 commit comments

Comments
 (0)