1
Fork 0

Be precise about usefulness vs reachability

This commit is contained in:
Nadrieril 2023-11-18 04:17:50 +01:00
parent 4e376cc104
commit 9aafc0b815
3 changed files with 159 additions and 94 deletions

View file

@ -1,6 +1,6 @@
use super::deconstruct_pat::{Constructor, DeconstructedPat, WitnessPat}; use super::deconstruct_pat::{Constructor, DeconstructedPat, WitnessPat};
use super::usefulness::{ use super::usefulness::{
compute_match_usefulness, MatchArm, MatchCheckCtxt, Reachability, UsefulnessReport, compute_match_usefulness, MatchArm, MatchCheckCtxt, Usefulness, UsefulnessReport,
}; };
use crate::errors::*; use crate::errors::*;
@ -749,18 +749,18 @@ fn report_arm_reachability<'p, 'tcx>(
); );
}; };
use Reachability::*; use Usefulness::*;
let mut catchall = None; let mut catchall = None;
for (arm, is_useful) in report.arm_usefulness.iter() { for (arm, is_useful) in report.arm_usefulness.iter() {
match is_useful { match is_useful {
Unreachable => report_unreachable_pattern(arm.pat.span(), arm.hir_id, catchall), Redundant => report_unreachable_pattern(arm.pat.span(), arm.hir_id, catchall),
Reachable(unreachables) if unreachables.is_empty() => {} Useful(redundant_spans) if redundant_spans.is_empty() => {}
// The arm is reachable, but contains unreachable subpatterns (from or-patterns). // The arm is reachable, but contains redundant subpatterns (from or-patterns).
Reachable(unreachables) => { Useful(redundant_spans) => {
let mut unreachables = unreachables.clone(); let mut redundant_spans = redundant_spans.clone();
// Emit lints in the order in which they occur in the file. // Emit lints in the order in which they occur in the file.
unreachables.sort_unstable(); redundant_spans.sort_unstable();
for span in unreachables { for span in redundant_spans {
report_unreachable_pattern(span, arm.hir_id, None); report_unreachable_pattern(span, arm.hir_id, None);
} }
} }

View file

@ -1339,7 +1339,8 @@ pub(crate) struct DeconstructedPat<'p, 'tcx> {
fields: Fields<'p, 'tcx>, fields: Fields<'p, 'tcx>,
ty: Ty<'tcx>, ty: Ty<'tcx>,
span: Span, span: Span,
reachable: Cell<bool>, /// Whether removing this arm would change the behavior of the match expression.
useful: Cell<bool>,
} }
impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
@ -1353,7 +1354,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
ty: Ty<'tcx>, ty: Ty<'tcx>,
span: Span, span: Span,
) -> Self { ) -> Self {
DeconstructedPat { ctor, fields, ty, span, reachable: Cell::new(false) } DeconstructedPat { ctor, fields, ty, span, useful: Cell::new(false) }
} }
/// Note: the input patterns must have been lowered through /// Note: the input patterns must have been lowered through
@ -1634,38 +1635,38 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
} }
} }
/// We keep track for each pattern if it was ever reachable during the analysis. This is used /// We keep track for each pattern if it was ever useful during the analysis. This is used
/// with `unreachable_spans` to report unreachable subpatterns arising from or patterns. /// with `redundant_spans` to report redundant subpatterns arising from or patterns.
pub(super) fn set_reachable(&self) { pub(super) fn set_useful(&self) {
self.reachable.set(true) self.useful.set(true)
} }
pub(super) fn is_reachable(&self) -> bool { pub(super) fn is_useful(&self) -> bool {
if self.reachable.get() { if self.useful.get() {
true true
} else if self.is_or_pat() && self.iter_fields().any(|f| f.is_reachable()) { } else if self.is_or_pat() && self.iter_fields().any(|f| f.is_useful()) {
// We always expand or patterns in the matrix, so we will never see the actual // We always expand or patterns in the matrix, so we will never see the actual
// or-pattern (the one with constructor `Or`) in the column. As such, it will not be // or-pattern (the one with constructor `Or`) in the column. As such, it will not be
// marked as reachable itself, only its children will. We recover this information here. // marked as useful itself, only its children will. We recover this information here.
self.set_reachable(); self.set_useful();
true true
} else { } else {
false false
} }
} }
/// Report the spans of subpatterns that were not reachable, if any. /// Report the spans of subpatterns that were not useful, if any.
pub(super) fn unreachable_spans(&self) -> Vec<Span> { pub(super) fn redundant_spans(&self) -> Vec<Span> {
let mut spans = Vec::new(); let mut spans = Vec::new();
self.collect_unreachable_spans(&mut spans); self.collect_redundant_spans(&mut spans);
spans spans
} }
fn collect_unreachable_spans(&self, spans: &mut Vec<Span>) { fn collect_redundant_spans(&self, spans: &mut Vec<Span>) {
// We don't look at subpatterns if we already reported the whole pattern as unreachable. // We don't look at subpatterns if we already reported the whole pattern as redundant.
if !self.is_reachable() { if !self.is_useful() {
spans.push(self.span); spans.push(self.span);
} else { } else {
for p in self.iter_fields() { for p in self.iter_fields() {
p.collect_unreachable_spans(spans); p.collect_redundant_spans(spans);
} }
} }
} }

View file

@ -1,8 +1,8 @@
//! # Match exhaustiveness and reachability algorithm //! # Match exhaustiveness and redundancy algorithm
//! //!
//! This file contains the logic for exhaustiveness and reachability checking for pattern-matching. //! This file contains the logic for exhaustiveness and usefulness checking for pattern-matching.
//! Specifically, given a list of patterns in a match, we can tell whether: //! Specifically, given a list of patterns in a match, we can tell whether:
//! (a) a given pattern is reachable (reachability) //! (a) a given pattern is redundant
//! (b) the patterns cover every possible value for the type (exhaustiveness) //! (b) the patterns cover every possible value for the type (exhaustiveness)
//! //!
//! The algorithm implemented here is inspired from the one described in [this //! The algorithm implemented here is inspired from the one described in [this
@ -19,15 +19,15 @@
//! The algorithm is given as input a list of patterns, one for each arm of a match, and computes //! The algorithm is given as input a list of patterns, one for each arm of a match, and computes
//! the following: //! the following:
//! - a set of values that match none of the patterns (if any), //! - a set of values that match none of the patterns (if any),
//! - for each subpattern (taking into account or-patterns), whether it would catch any value that //! - for each subpattern (taking into account or-patterns), whether removing it would change
//! isn't caught by a pattern before it, i.e. whether it is reachable. //! anything about how the match executes, i.e. whether it is useful/not redundant.
//! //!
//! To a first approximation, the algorithm works by exploring all possible values for the type //! To a first approximation, the algorithm works by exploring all possible values for the type
//! being matched on, and determining which arm(s) catch which value. To make this tractable we //! being matched on, and determining which arm(s) catch which value. To make this tractable we
//! cleverly group together values, as we'll see below. //! cleverly group together values, as we'll see below.
//! //!
//! The entrypoint of this file is the [`compute_match_usefulness`] function, which computes //! The entrypoint of this file is the [`compute_match_usefulness`] function, which computes
//! reachability for each subpattern and exhaustiveness for the whole match. //! usefulness for each subpattern and exhaustiveness for the whole match.
//! //!
//! In this page we explain the necessary concepts to understand how the algorithm works. //! In this page we explain the necessary concepts to understand how the algorithm works.
//! //!
@ -39,17 +39,17 @@
//! none of the `p_i`. We write `usefulness(p_1 .. p_n, q)` for a function that returns a list of //! none of the `p_i`. We write `usefulness(p_1 .. p_n, q)` for a function that returns a list of
//! such values. The aim of this file is to compute it efficiently. //! such values. The aim of this file is to compute it efficiently.
//! //!
//! This is enough to compute reachability: a pattern in a `match` expression is reachable iff it is //! This is enough to compute usefulness: a pattern in a `match` expression is redundant iff it is
//! useful w.r.t. the patterns above it: //! not useful w.r.t. the patterns above it:
//! ```compile_fail,E0004 //! ```compile_fail,E0004
//! # #![feature(exclusive_range_pattern)] //! # #![feature(exclusive_range_pattern)]
//! # fn foo() { //! # fn foo() {
//! match Some(0u32) { //! match Some(0u32) {
//! Some(0..100) => {}, //! Some(0..100) => {},
//! Some(90..190) => {}, // reachable: `Some(150)` is matched by this but not the branch above //! Some(90..190) => {}, // useful: `Some(150)` is matched by this but not the branch above
//! Some(50..150) => {}, // unreachable: all the values this matches are already matched by //! Some(50..150) => {}, // redundant: all the values this matches are already matched by
//! // the branches above //! // the branches above
//! None => {}, // reachable: `None` is matched by this but not the branches above //! None => {}, // useful: `None` is matched by this but not the branches above
//! } //! }
//! # } //! # }
//! ``` //! ```
@ -246,18 +246,17 @@
//! //!
//! //!
//! //!
//! # Computing reachability and exhaustiveness in one go //! # Computing usefulness and exhaustiveness in one go
//! //!
//! The algorithm we have described so far computes usefulness of each pattern in turn to check if //! The algorithm we have described so far computes usefulness of each pattern in turn, and ends by
//! it is reachable, and ends by checking if `_` is useful to determine exhaustiveness of the whole //! checking if `_` is useful to determine exhaustiveness of the whole match. In practice, instead
//! match. In practice, instead of doing "for each pattern { for each constructor { ... } }", we do //! of doing "for each pattern { for each constructor { ... } }", we do "for each constructor { for
//! "for each constructor { for each pattern { ... } }". This allows us to compute everything in one //! each pattern { ... } }". This allows us to compute everything in one go.
//! go.
//! //!
//! [`Matrix`] stores the set of pattern-tuples under consideration. We track reachability of each //! [`Matrix`] stores the set of pattern-tuples under consideration. We track usefulness of each
//! row mutably in the matrix as we go along. We ignore witnesses of usefulness of the match rows. //! row mutably in the matrix as we go along. We ignore witnesses of usefulness of the match rows.
//! We gather witnesses of the usefulness of `_` in [`WitnessMatrix`]. The algorithm that computes //! We gather witnesses of the usefulness of `_` in [`WitnessMatrix`]. The algorithm that computes
//! all this is in [`compute_exhaustiveness_and_reachability`]. //! all this is in [`compute_exhaustiveness_and_usefulness`].
//! //!
//! See the full example at the bottom of this documentation. //! See the full example at the bottom of this documentation.
//! //!
@ -279,7 +278,7 @@
//! ``` //! ```
//! //!
//! In this example, trying any of `0`, `1`, .., `49` will give the same specialized matrix, and //! In this example, trying any of `0`, `1`, .., `49` will give the same specialized matrix, and
//! thus the same reachability/exhaustiveness results. We can thus accelerate the algorithm by //! thus the same usefulness/exhaustiveness results. We can thus accelerate the algorithm by
//! trying them all at once. Here in fact, the only cases we need to consider are: `0..50`, //! trying them all at once. Here in fact, the only cases we need to consider are: `0..50`,
//! `50..=100`, `101..=150`,`151..=200` and `201..`. //! `50..=100`, `101..=150`,`151..=200` and `201..`.
//! //!
@ -299,15 +298,16 @@
//! This is done in [`ConstructorSet::split`] and explained in [`super::deconstruct_pat`]. //! This is done in [`ConstructorSet::split`] and explained in [`super::deconstruct_pat`].
//! //!
//! //!
//!
//! # Or-patterns //! # Or-patterns
//! //!
//! What we have described so far works well if there are no or-patterns. To handle them, if the //! What we have described so far works well if there are no or-patterns. To handle them, if the
//! first pattern of a row in the matrix is an or-pattern, we expand it by duplicating the rest of //! first pattern of a row in the matrix is an or-pattern, we expand it by duplicating the rest of
//! the row as necessary. This is handled automatically in [`Matrix`]. //! the row as necessary. This is handled automatically in [`Matrix`].
//! //!
//! This makes reachability tracking subtle, because we also want to compute whether an alternative //! This makes usefulness tracking subtle, because we also want to compute whether an alternative
//! of an or-pattern is unreachable, e.g. in `Some(_) | Some(0)`. We track reachability of each //! of an or-pattern is redundant, e.g. in `Some(_) | Some(0)`. We track usefulness of each
//! subpattern by interior mutability in [`DeconstructedPat`] with `set_reachable`/`is_reachable`. //! subpattern by interior mutability in [`DeconstructedPat`] with `set_useful`/`is_useful`.
//! //!
//! It's unfortunate that we have to use interior mutability, but believe me (Nadrieril), I have //! It's unfortunate that we have to use interior mutability, but believe me (Nadrieril), I have
//! tried [other](https://github.com/rust-lang/rust/pull/80104) //! tried [other](https://github.com/rust-lang/rust/pull/80104)
@ -332,6 +332,69 @@
//! //!
//! //!
//! //!
//! # Usefulness vs reachability, validity, and empty patterns
//!
//! This is likely the subtlest aspect of the algorithm. To be fully precise, a match doesn't
//! operate on a value, it operates on a place. In certain unsafe circumstances, it is possible for
//! a place to not contain valid data for its type. This has subtle consequences for empty types.
//! Take the following:
//!
//! ```rust
//! enum Void {}
//! let x: u8 = 0;
//! let ptr: *const Void = &x as *const u8 as *const Void;
//! unsafe {
//! match *ptr {
//! _ => println!("Reachable!"),
//! }
//! }
//! ```
//!
//! In this example, `ptr` is a valid pointer pointing to a place with invalid data. The `_` pattern
//! does not look at the contents of `*ptr`, so this is ok and the arm is taken. In other words,
//! despite the place we are inspecting being of type `Void`, there is a reachable arm. If the
//! arm had a binding however:
//!
//! ```rust
//! # #[derive(Copy, Clone)]
//! # enum Void {}
//! # let x: u8 = 0;
//! # let ptr: *const Void = &x as *const u8 as *const Void;
//! # unsafe {
//! match *ptr {
//! _a => println!("Unreachable!"),
//! }
//! # }
//! ```
//!
//! Here the binding loads the value of type `Void` from the `*ptr` place. In this example, this
//! causes UB since the data is not valid. In the general case, this asserts validity of the data at
//! `*ptr`. Either way, this arm will never be taken.
//!
//! Finally, let's consider the empty match `match *ptr {}`. If we consider this exhaustive, then
//! having invalid data at `*ptr` is invalid. In other words, the empty match is semantically
//! equivalent to the `_a => ...` match. In the interest of explicitness, we prefer the case with an
//! arm, hence we won't tell the user to remove the `_a` arm. In other words, the `_a` arm is
//! unreachable yet not redundant. This is why we lint on redundant arms rather than unreachable
//! arms, despite the fact that the lint says "unreachable".
//!
//! These considerations only affects certain places, namely those that can contain non-valid data
//! without UB. These are: pointer dereferences, reference dereferences, and union field accesses.
//! We track in the algorithm whether a given place is known to contain valid data. This is done
//! first by inspecting the scrutinee syntactically (which gives us `cx.known_valid_scrutinee`), and
//! then by tracking validity of each column of the matrix (which correspond to places) as we
//! recurse into subpatterns. That second part is done through [`ValidityConstraint`], most notably
//! [`ValidityConstraint::specialize`].
//!
//! Having said all that, in practice we don't fully follow what's been presented in this section.
//! Under `exhaustive_patterns`, we allow omitting empty arms even in `!known_valid` places, for
//! backwards-compatibility until we have a better alternative. Without `exhaustive_patterns`, we
//! mostly treat empty types as inhabited, except specifically a non-nested `!` or empty enum. In
//! this specific case we also allow the empty match regardless of place validity, for
//! backwards-compatibility. Hopefully we can eventually deprecate this.
//!
//!
//!
//! # Full example //! # Full example
//! //!
//! We illustrate a full run of the algorithm on the following match. //! We illustrate a full run of the algorithm on the following match.
@ -348,7 +411,7 @@
//! ``` //! ```
//! //!
//! We keep track of the original row for illustration purposes, this is not what the algorithm //! We keep track of the original row for illustration purposes, this is not what the algorithm
//! actually does (it tracks reachability as a boolean on each row). //! actually does (it tracks usefulness as a boolean on each row).
//! //!
//! ```text //! ```text
//! ┐ Patterns: //! ┐ Patterns:
@ -377,7 +440,7 @@
//! │ │ │ ├─┐ Patterns: //! │ │ │ ├─┐ Patterns:
//! │ │ │ │ │ 1. `[]` //! │ │ │ │ │ 1. `[]`
//! │ │ │ │ │ //! │ │ │ │ │
//! │ │ │ │ │ We note arm 1 is reachable (by `Pair(Some(0), true)`). //! │ │ │ │ │ We note arm 1 is useful (by `Pair(Some(0), true)`).
//! │ │ │ ├─┘ //! │ │ │ ├─┘
//! │ │ │ │ //! │ │ │ │
//! │ │ │ │ Specialize with `false`: //! │ │ │ │ Specialize with `false`:
@ -385,7 +448,7 @@
//! │ │ │ │ │ 1. `[]` //! │ │ │ │ │ 1. `[]`
//! │ │ │ │ │ 3. `[]` //! │ │ │ │ │ 3. `[]`
//! │ │ │ │ │ //! │ │ │ │ │
//! │ │ │ │ │ We note arm 1 is reachable (by `Pair(Some(0), false)`). //! │ │ │ │ │ We note arm 1 is useful (by `Pair(Some(0), false)`).
//! │ │ │ ├─┘ //! │ │ │ ├─┘
//! │ │ ├─┘ //! │ │ ├─┘
//! │ │ │ //! │ │ │
@ -408,7 +471,7 @@
//! │ │ │ ├─┐ Patterns: //! │ │ │ ├─┐ Patterns:
//! │ │ │ │ │ 2. `[]` //! │ │ │ │ │ 2. `[]`
//! │ │ │ │ │ //! │ │ │ │ │
//! │ │ │ │ │ We note arm 2 is reachable (by `Pair(Some(1..), false)`). //! │ │ │ │ │ We note arm 2 is useful (by `Pair(Some(1..), false)`).
//! │ │ │ ├─┘ //! │ │ │ ├─┘
//! │ │ │ │ //! │ │ │ │
//! │ │ │ │ Total witnesses for `1..`: //! │ │ │ │ Total witnesses for `1..`:
@ -442,7 +505,7 @@
//! │ │ ├─┐ Patterns: //! │ │ ├─┐ Patterns:
//! │ │ │ │ 2. `[]` //! │ │ │ │ 2. `[]`
//! │ │ │ │ //! │ │ │ │
//! │ │ │ │ We note arm 2 is reachable (by `Pair(None, false)`). //! │ │ │ │ We note arm 2 is useful (by `Pair(None, false)`).
//! │ │ ├─┘ //! │ │ ├─┘
//! │ │ │ //! │ │ │
//! │ │ │ Total witnesses for `None`: //! │ │ │ Total witnesses for `None`:
@ -466,7 +529,7 @@
//! ``` //! ```
//! //!
//! We conclude: //! We conclude:
//! - Arm 3 is unreachable (it was never marked as reachable); //! - Arm 3 is redundant (it was never marked as useful);
//! - The match is not exhaustive; //! - The match is not exhaustive;
//! - Adding arms with `Pair(Some(1..), true)` and `Pair(None, true)` would make the match exhaustive. //! - Adding arms with `Pair(Some(1..), true)` and `Pair(None, true)` would make the match exhaustive.
//! //!
@ -639,13 +702,13 @@ struct MatrixRow<'p, 'tcx> {
/// Whether the original arm had a guard. This is inherited when specializing. /// Whether the original arm had a guard. This is inherited when specializing.
is_under_guard: bool, is_under_guard: bool,
/// When we specialize, we remember which row of the original matrix produced a given row of the /// When we specialize, we remember which row of the original matrix produced a given row of the
/// specialized matrix. When we unspecialize, we use this to propagate reachability back up the /// specialized matrix. When we unspecialize, we use this to propagate usefulness back up the
/// callstack. /// callstack.
parent_row: usize, parent_row: usize,
/// False when the matrix is just built. This is set to `true` by /// False when the matrix is just built. This is set to `true` by
/// [`compute_exhaustiveness_and_reachability`] if the arm is found to be reachable. /// [`compute_exhaustiveness_and_usefulness`] if the arm is found to be useful.
/// This is reset to `false` when specializing. /// This is reset to `false` when specializing.
reachable: bool, useful: bool,
} }
impl<'p, 'tcx> MatrixRow<'p, 'tcx> { impl<'p, 'tcx> MatrixRow<'p, 'tcx> {
@ -672,7 +735,7 @@ impl<'p, 'tcx> MatrixRow<'p, 'tcx> {
pats: patstack, pats: patstack,
parent_row: self.parent_row, parent_row: self.parent_row,
is_under_guard: self.is_under_guard, is_under_guard: self.is_under_guard,
reachable: false, useful: false,
}) })
} }
@ -688,7 +751,7 @@ impl<'p, 'tcx> MatrixRow<'p, 'tcx> {
pats: self.pats.pop_head_constructor(pcx, ctor), pats: self.pats.pop_head_constructor(pcx, ctor),
parent_row, parent_row,
is_under_guard: self.is_under_guard, is_under_guard: self.is_under_guard,
reachable: false, useful: false,
} }
} }
} }
@ -741,7 +804,7 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> {
pats: PatStack::from_pattern(arm.pat), pats: PatStack::from_pattern(arm.pat),
parent_row: row_id, // dummy, we won't read it parent_row: row_id, // dummy, we won't read it
is_under_guard: arm.has_guard, is_under_guard: arm.has_guard,
reachable: false, useful: false,
}; };
matrix.expand_and_push(v); matrix.expand_and_push(v);
} }
@ -940,7 +1003,7 @@ impl<'tcx> WitnessStack<'tcx> {
/// Represents a set of pattern-tuples that are witnesses of non-exhaustiveness for error /// Represents a set of pattern-tuples that are witnesses of non-exhaustiveness for error
/// reporting. This has similar invariants as `Matrix` does. /// reporting. This has similar invariants as `Matrix` does.
/// ///
/// The `WitnessMatrix` returned by [`compute_exhaustiveness_and_reachability`] obeys the invariant /// The `WitnessMatrix` returned by [`compute_exhaustiveness_and_usefulness`] obeys the invariant
/// that the union of the input `Matrix` and the output `WitnessMatrix` together matches the type /// that the union of the input `Matrix` and the output `WitnessMatrix` together matches the type
/// exhaustively. /// exhaustively.
/// ///
@ -1029,7 +1092,7 @@ impl<'tcx> WitnessMatrix<'tcx> {
/// The core of the algorithm. /// The core of the algorithm.
/// ///
/// This recursively computes witnesses of the non-exhaustiveness of `matrix` (if any). Also tracks /// This recursively computes witnesses of the non-exhaustiveness of `matrix` (if any). Also tracks
/// usefulness of each row in the matrix (in `row.reachable`). We track reachability of each /// usefulness of each row in the matrix (in `row.useful`). We track usefulness of each
/// subpattern using interior mutability in `DeconstructedPat`. /// subpattern using interior mutability in `DeconstructedPat`.
/// ///
/// The input `Matrix` and the output `WitnessMatrix` together match the type exhaustively. /// The input `Matrix` and the output `WitnessMatrix` together match the type exhaustively.
@ -1038,10 +1101,10 @@ impl<'tcx> WitnessMatrix<'tcx> {
/// - specialization, where we dig into the rows that have a specific constructor and call ourselves /// - specialization, where we dig into the rows that have a specific constructor and call ourselves
/// recursively; /// recursively;
/// - unspecialization, where we lift the results from the previous step into results for this step /// - unspecialization, where we lift the results from the previous step into results for this step
/// (using `apply_constructor` and by updating `row.reachable` for each parent row). /// (using `apply_constructor` and by updating `row.useful` for each parent row).
/// This is all explained at the top of the file. /// This is all explained at the top of the file.
#[instrument(level = "debug", skip(cx, is_top_level), ret)] #[instrument(level = "debug", skip(cx, is_top_level), ret)]
fn compute_exhaustiveness_and_reachability<'p, 'tcx>( fn compute_exhaustiveness_and_usefulness<'p, 'tcx>(
cx: &MatchCheckCtxt<'p, 'tcx>, cx: &MatchCheckCtxt<'p, 'tcx>,
matrix: &mut Matrix<'p, 'tcx>, matrix: &mut Matrix<'p, 'tcx>,
is_top_level: bool, is_top_level: bool,
@ -1050,10 +1113,10 @@ fn compute_exhaustiveness_and_reachability<'p, 'tcx>(
let Some(ty) = matrix.head_ty() else { let Some(ty) = matrix.head_ty() else {
// The base case: there are no columns in the matrix. We are morally pattern-matching on (). // The base case: there are no columns in the matrix. We are morally pattern-matching on ().
// A row is reachable iff it has no (unguarded) rows above it. // A row is useful iff it has no (unguarded) rows above it.
for row in matrix.rows_mut() { for row in matrix.rows_mut() {
// All rows are reachable until we find one without a guard. // All rows are useful until they're not.
row.reachable = true; row.useful = true;
if !row.is_under_guard { if !row.is_under_guard {
// There's an unguarded row, so the match is exhaustive, and any subsequent row is // There's an unguarded row, so the match is exhaustive, and any subsequent row is
// unreachable. // unreachable.
@ -1095,7 +1158,7 @@ fn compute_exhaustiveness_and_reachability<'p, 'tcx>(
// Dig into rows that match `ctor`. // Dig into rows that match `ctor`.
let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor);
let mut witnesses = ensure_sufficient_stack(|| { let mut witnesses = ensure_sufficient_stack(|| {
compute_exhaustiveness_and_reachability(cx, &mut spec_matrix, false) compute_exhaustiveness_and_usefulness(cx, &mut spec_matrix, false)
}); });
if !only_report_missing || matches!(ctor, Constructor::Missing) { if !only_report_missing || matches!(ctor, Constructor::Missing) {
@ -1113,14 +1176,14 @@ fn compute_exhaustiveness_and_reachability<'p, 'tcx>(
// A parent row is useful if any of its children is. // A parent row is useful if any of its children is.
for child_row in spec_matrix.rows() { for child_row in spec_matrix.rows() {
let parent_row = &mut matrix.rows[child_row.parent_row]; let parent_row = &mut matrix.rows[child_row.parent_row];
parent_row.reachable = parent_row.reachable || child_row.reachable; parent_row.useful = parent_row.useful || child_row.useful;
} }
} }
// Record that the subpattern is reachable. // Record usefulness in the patterns.
for row in matrix.rows() { for row in matrix.rows() {
if row.reachable { if row.useful {
row.head().set_reachable(); row.head().set_useful();
} }
} }
@ -1130,8 +1193,8 @@ fn compute_exhaustiveness_and_reachability<'p, 'tcx>(
/// A column of patterns in the matrix, where a column is the intuitive notion of "subpatterns that /// A column of patterns in the matrix, where a column is the intuitive notion of "subpatterns that
/// inspect the same subvalue/place". /// inspect the same subvalue/place".
/// This is used to traverse patterns column-by-column for lints. Despite similarities with /// This is used to traverse patterns column-by-column for lints. Despite similarities with
/// [`compute_exhaustiveness_and_reachability`], this does a different traversal. Notably this is /// [`compute_exhaustiveness_and_usefulness`], this does a different traversal. Notably this is
/// linear in the depth of patterns, whereas `compute_exhaustiveness_and_reachability` is worst-case /// linear in the depth of patterns, whereas `compute_exhaustiveness_and_usefulness` is worst-case
/// exponential (exhaustiveness is NP-complete). The core difference is that we treat sub-columns /// exponential (exhaustiveness is NP-complete). The core difference is that we treat sub-columns
/// separately. /// separately.
/// ///
@ -1351,28 +1414,29 @@ pub(crate) struct MatchArm<'p, 'tcx> {
pub(crate) has_guard: bool, pub(crate) has_guard: bool,
} }
/// Indicates whether or not a given arm is reachable. /// Indicates whether or not a given arm is useful.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) enum Reachability { pub(crate) enum Usefulness {
/// The arm is reachable. This additionally carries a set of or-pattern branches that have been /// The arm is useful. This additionally carries a set of or-pattern branches that have been
/// found to be unreachable despite the overall arm being reachable. Used only in the presence /// found to be redundant despite the overall arm being useful. Used only in the presence of
/// of or-patterns, otherwise it stays empty. /// or-patterns, otherwise it stays empty.
Reachable(Vec<Span>), Useful(Vec<Span>),
/// The arm is unreachable. /// The arm is redundant and can be removed without changing the behavior of the match
Unreachable, /// expression.
Redundant,
} }
/// The output of checking a match for exhaustiveness and arm reachability. /// The output of checking a match for exhaustiveness and arm usefulness.
pub(crate) struct UsefulnessReport<'p, 'tcx> { pub(crate) struct UsefulnessReport<'p, 'tcx> {
/// For each arm of the input, whether that arm is reachable after the arms above it. /// For each arm of the input, whether that arm is useful after the arms above it.
pub(crate) arm_usefulness: Vec<(MatchArm<'p, 'tcx>, Reachability)>, pub(crate) arm_usefulness: Vec<(MatchArm<'p, 'tcx>, Usefulness)>,
/// If the match is exhaustive, this is empty. If not, this contains witnesses for the lack of /// If the match is exhaustive, this is empty. If not, this contains witnesses for the lack of
/// exhaustiveness. /// exhaustiveness.
pub(crate) non_exhaustiveness_witnesses: Vec<WitnessPat<'tcx>>, pub(crate) non_exhaustiveness_witnesses: Vec<WitnessPat<'tcx>>,
} }
/// The entrypoint for this file. Computes whether a match is exhaustive and which of its arms are /// The entrypoint for this file. Computes whether a match is exhaustive and which of its arms are
/// reachable. /// useful.
#[instrument(skip(cx, arms), level = "debug")] #[instrument(skip(cx, arms), level = "debug")]
pub(crate) fn compute_match_usefulness<'p, 'tcx>( pub(crate) fn compute_match_usefulness<'p, 'tcx>(
cx: &MatchCheckCtxt<'p, 'tcx>, cx: &MatchCheckCtxt<'p, 'tcx>,
@ -1380,8 +1444,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
scrut_ty: Ty<'tcx>, scrut_ty: Ty<'tcx>,
) -> UsefulnessReport<'p, 'tcx> { ) -> UsefulnessReport<'p, 'tcx> {
let mut matrix = Matrix::new(cx, arms, scrut_ty); let mut matrix = Matrix::new(cx, arms, scrut_ty);
let non_exhaustiveness_witnesses = let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(cx, &mut matrix, true);
compute_exhaustiveness_and_reachability(cx, &mut matrix, true);
let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column();
let arm_usefulness: Vec<_> = arms let arm_usefulness: Vec<_> = arms
@ -1389,12 +1452,13 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
.copied() .copied()
.map(|arm| { .map(|arm| {
debug!(?arm); debug!(?arm);
let reachability = if arm.pat.is_reachable() { // We warn when a pattern is not useful.
Reachability::Reachable(arm.pat.unreachable_spans()) let usefulness = if arm.pat.is_useful() {
Usefulness::Useful(arm.pat.redundant_spans())
} else { } else {
Reachability::Unreachable Usefulness::Redundant
}; };
(arm, reachability) (arm, usefulness)
}) })
.collect(); .collect();
let report = UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses }; let report = UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses };