Be precise about usefulness vs reachability
This commit is contained in:
parent
4e376cc104
commit
9aafc0b815
3 changed files with 159 additions and 94 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue