Skip to content

Commit 49ee7bc

Browse files
committed
Auto merge of #128034 - Nadrieril:explain-unreachable, r=<try>
exhaustiveness: Explain why a given pattern is considered unreachable This PR tells the user why a given pattern is considered unreachable. I reused the intersection information we were already computing; even though it's incomplete I convinced myself that it is sufficient to always get a set of patterns that cover the unreachable one. I'm not a fan of the diagnostic messages I came up with, I'm open to suggestions. Fixes #127870. This is also the other one of the two diagnostic improvements I wanted to do before #122792. Note: the first commit is #128015, the second is an unrelated drive-by tweak. r? `@compiler-errors`
2 parents 92c6c03 + fbc2f03 commit 49ee7bc

File tree

53 files changed

+1468
-400
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1468
-400
lines changed

compiler/rustc_mir_build/messages.ftl

+4-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,10 @@ mir_build_union_pattern = cannot use unions in constant patterns
327327
328328
mir_build_unreachable_pattern = unreachable pattern
329329
.label = unreachable pattern
330-
.catchall_label = matches any value
330+
.unreachable_matches_no_values = this pattern matches no values because `{$ty}` is uninhabited
331+
.unreachable_covered_by_catchall = matches any value
332+
.unreachable_covered_by_one = matches all the values already
333+
.unreachable_covered_by_many = matches some of the same values
331334
332335
mir_build_unsafe_fn_safe_body = an unsafe function restricts its caller, but its body is safe by default
333336
mir_build_unsafe_not_inherited = items do not inherit unsafety from separate enclosing items

compiler/rustc_mir_build/src/errors.rs

+29-3
Original file line numberDiff line numberDiff line change
@@ -582,11 +582,37 @@ pub(crate) struct NonConstPath {
582582

583583
#[derive(LintDiagnostic)]
584584
#[diag(mir_build_unreachable_pattern)]
585-
pub(crate) struct UnreachablePattern {
585+
pub(crate) struct UnreachablePattern<'tcx> {
586586
#[label]
587587
pub(crate) span: Option<Span>,
588-
#[label(mir_build_catchall_label)]
589-
pub(crate) catchall: Option<Span>,
588+
#[subdiagnostic]
589+
pub(crate) matches_no_values: Option<UnreachableMatchesNoValues<'tcx>>,
590+
#[label(mir_build_unreachable_covered_by_catchall)]
591+
pub(crate) covered_by_catchall: Option<Span>,
592+
#[label(mir_build_unreachable_covered_by_one)]
593+
pub(crate) covered_by_one: Option<Span>,
594+
#[subdiagnostic]
595+
pub(crate) covered_by_many: Option<UnreachableCoveredByMany>,
596+
}
597+
598+
#[derive(Subdiagnostic)]
599+
#[note(mir_build_unreachable_matches_no_values)]
600+
pub(crate) struct UnreachableMatchesNoValues<'tcx> {
601+
pub(crate) ty: Ty<'tcx>,
602+
}
603+
604+
pub(crate) struct UnreachableCoveredByMany(pub(crate) Vec<Span>);
605+
606+
impl Subdiagnostic for UnreachableCoveredByMany {
607+
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
608+
self,
609+
diag: &mut Diag<'_, G>,
610+
_f: &F,
611+
) {
612+
for span in self.0 {
613+
diag.span_label(span, fluent::mir_build_unreachable_covered_by_many);
614+
}
615+
}
590616
}
591617

592618
#[derive(Diagnostic)]

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

+55-25
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use rustc_middle::ty::print::with_no_trimmed_paths;
1616
use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt};
1717
use rustc_pattern_analysis::errors::Uncovered;
1818
use rustc_pattern_analysis::rustc::{
19-
Constructor, DeconstructedPat, MatchArm, RustcPatCtxt as PatCtxt, Usefulness, UsefulnessReport,
20-
WitnessPat,
19+
Constructor, DeconstructedPat, MatchArm, RedundancyExplanation, RustcPatCtxt as PatCtxt,
20+
Usefulness, UsefulnessReport, WitnessPat,
2121
};
2222
use rustc_session::lint::builtin::{
2323
BINDINGS_WITH_VARIANT_NAME, IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS,
@@ -391,12 +391,16 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> {
391391
) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
392392
let pattern_complexity_limit =
393393
get_limit_size(cx.tcx.hir().krate_attrs(), cx.tcx.sess, sym::pattern_complexity);
394-
let report =
395-
rustc_pattern_analysis::analyze_match(&cx, &arms, scrut_ty, pattern_complexity_limit)
396-
.map_err(|err| {
397-
self.error = Err(err);
398-
err
399-
})?;
394+
let report = rustc_pattern_analysis::rustc::analyze_match(
395+
&cx,
396+
&arms,
397+
scrut_ty,
398+
pattern_complexity_limit,
399+
)
400+
.map_err(|err| {
401+
self.error = Err(err);
402+
err
403+
})?;
400404

401405
// Warn unreachable subpatterns.
402406
for (arm, is_useful) in report.arm_usefulness.iter() {
@@ -405,9 +409,9 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> {
405409
{
406410
let mut redundant_subpats = redundant_subpats.clone();
407411
// Emit lints in the order in which they occur in the file.
408-
redundant_subpats.sort_unstable_by_key(|pat| pat.data().span);
409-
for pat in redundant_subpats {
410-
report_unreachable_pattern(cx, arm.arm_data, pat.data().span, None)
412+
redundant_subpats.sort_unstable_by_key(|(pat, _)| pat.data().span);
413+
for (pat, explanation) in redundant_subpats {
414+
report_unreachable_pattern(cx, arm.arm_data, pat, &explanation)
411415
}
412416
}
413417
}
@@ -906,26 +910,52 @@ fn report_irrefutable_let_patterns(
906910
fn report_unreachable_pattern<'p, 'tcx>(
907911
cx: &PatCtxt<'p, 'tcx>,
908912
hir_id: HirId,
909-
span: Span,
910-
catchall: Option<Span>,
913+
pat: &DeconstructedPat<'p, 'tcx>,
914+
explanation: &RedundancyExplanation<'p, 'tcx>,
911915
) {
912-
cx.tcx.emit_node_span_lint(
913-
UNREACHABLE_PATTERNS,
914-
hir_id,
915-
span,
916-
UnreachablePattern { span: if catchall.is_some() { Some(span) } else { None }, catchall },
917-
);
916+
let pat_span = pat.data().span;
917+
let mut lint = UnreachablePattern {
918+
span: Some(pat_span),
919+
matches_no_values: None,
920+
covered_by_catchall: None,
921+
covered_by_one: None,
922+
covered_by_many: None,
923+
};
924+
match explanation.covered_by.as_slice() {
925+
[] => {
926+
// Empty pattern; we report the uninhabited type that caused the emptiness.
927+
lint.span = None; // Don't label the pattern itself
928+
pat.walk(&mut |subpat| {
929+
let ty = **subpat.ty();
930+
if cx.is_uninhabited(ty) {
931+
lint.matches_no_values = Some(UnreachableMatchesNoValues { ty });
932+
false // No need to dig further.
933+
} else if matches!(subpat.ctor(), Constructor::Ref | Constructor::UnionField) {
934+
false // Don't explore further since they are not by-value.
935+
} else {
936+
true
937+
}
938+
});
939+
}
940+
[covering_pat] if pat_is_catchall(covering_pat) => {
941+
lint.covered_by_catchall = Some(covering_pat.data().span);
942+
}
943+
[covering_pat] => {
944+
lint.covered_by_one = Some(covering_pat.data().span);
945+
}
946+
covering_pats => {
947+
let covering_spans = covering_pats.iter().map(|p| p.data().span).collect();
948+
lint.covered_by_many = Some(UnreachableCoveredByMany(covering_spans));
949+
}
950+
}
951+
cx.tcx.emit_node_span_lint(UNREACHABLE_PATTERNS, hir_id, pat_span, lint);
918952
}
919953

920954
/// Report unreachable arms, if any.
921955
fn report_arm_reachability<'p, 'tcx>(cx: &PatCtxt<'p, 'tcx>, report: &UsefulnessReport<'p, 'tcx>) {
922-
let mut catchall = None;
923956
for (arm, is_useful) in report.arm_usefulness.iter() {
924-
if matches!(is_useful, Usefulness::Redundant) {
925-
report_unreachable_pattern(cx, arm.arm_data, arm.pat.data().span, catchall)
926-
}
927-
if !arm.has_guard && catchall.is_none() && pat_is_catchall(arm.pat) {
928-
catchall = Some(arm.pat.data().span);
957+
if let Usefulness::Redundant(explanation) = is_useful {
958+
report_unreachable_pattern(cx, arm.arm_data, arm.pat, explanation)
929959
}
930960
}
931961
}

compiler/rustc_pattern_analysis/src/lib.rs

+3-34
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
//! Analysis of patterns, notably match exhaustiveness checking.
1+
//! Analysis of patterns, notably match exhaustiveness checking. The main entrypoint for this crate
2+
//! is [`usefulness::compute_match_usefulness`]. For rustc-specific types and entrypoints, see the
3+
//! [`rustc`] module.
24
35
// tidy-alphabetical-start
46
#![allow(rustc::diagnostic_outside_of_impl)]
@@ -23,14 +25,8 @@ use std::fmt;
2325

2426
pub use rustc_index::{Idx, IndexVec}; // re-exported to avoid rustc_index version issues
2527

26-
#[cfg(feature = "rustc")]
27-
use rustc_middle::ty::Ty;
28-
#[cfg(feature = "rustc")]
29-
use rustc_span::ErrorGuaranteed;
30-
3128
use crate::constructor::{Constructor, ConstructorSet, IntRange};
3229
use crate::pat::DeconstructedPat;
33-
use crate::pat_column::PatternColumn;
3430

3531
pub trait Captures<'a> {}
3632
impl<'a, T: ?Sized> Captures<'a> for T {}
@@ -128,30 +124,3 @@ impl<'p, Cx: PatCx> Clone for MatchArm<'p, Cx> {
128124
}
129125

130126
impl<'p, Cx: PatCx> Copy for MatchArm<'p, Cx> {}
131-
132-
/// The entrypoint for this crate. Computes whether a match is exhaustive and which of its arms are
133-
/// useful, and runs some lints.
134-
#[cfg(feature = "rustc")]
135-
pub fn analyze_match<'p, 'tcx>(
136-
tycx: &rustc::RustcPatCtxt<'p, 'tcx>,
137-
arms: &[rustc::MatchArm<'p, 'tcx>],
138-
scrut_ty: Ty<'tcx>,
139-
pattern_complexity_limit: Option<usize>,
140-
) -> Result<rustc::UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
141-
use lints::lint_nonexhaustive_missing_variants;
142-
use usefulness::{compute_match_usefulness, PlaceValidity};
143-
144-
let scrut_ty = tycx.reveal_opaque_ty(scrut_ty);
145-
let scrut_validity = PlaceValidity::from_bool(tycx.known_valid_scrutinee);
146-
let report =
147-
compute_match_usefulness(tycx, arms, scrut_ty, scrut_validity, pattern_complexity_limit)?;
148-
149-
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
150-
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
151-
if tycx.refutable && report.non_exhaustiveness_witnesses.is_empty() {
152-
let pat_column = PatternColumn::new(arms);
153-
lint_nonexhaustive_missing_variants(tycx, arms, &pat_column, scrut_ty)?;
154-
}
155-
156-
Ok(report)
157-
}

compiler/rustc_pattern_analysis/src/pat.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{PatCx, PrivateUninhabitedField};
1111
use self::Constructor::*;
1212

1313
/// A globally unique id to distinguish patterns.
14-
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
14+
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1515
pub(crate) struct PatId(u32);
1616
impl PatId {
1717
fn new() -> Self {
@@ -147,6 +147,21 @@ impl<Cx: PatCx> fmt::Debug for DeconstructedPat<Cx> {
147147
}
148148
}
149149

150+
/// Delegate to `uid`.
151+
impl<Cx: PatCx> PartialEq for DeconstructedPat<Cx> {
152+
fn eq(&self, other: &Self) -> bool {
153+
self.uid == other.uid
154+
}
155+
}
156+
/// Delegate to `uid`.
157+
impl<Cx: PatCx> Eq for DeconstructedPat<Cx> {}
158+
/// Delegate to `uid`.
159+
impl<Cx: PatCx> std::hash::Hash for DeconstructedPat<Cx> {
160+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
161+
self.uid.hash(state);
162+
}
163+
}
164+
150165
/// Represents either a pattern obtained from user input or a wildcard constructed during the
151166
/// algorithm. Do not use `Wild` to represent a wildcard pattern comping from user input.
152167
///
@@ -190,7 +205,18 @@ impl<'p, Cx: PatCx> PatOrWild<'p, Cx> {
190205
}
191206
}
192207

193-
/// Expand this (possibly-nested) or-pattern into its alternatives.
208+
/// Expand this or-pattern into its alternatives. This only expands one or-pattern; use
209+
/// `flatten_or_pat` to recursively expand nested or-patterns.
210+
pub(crate) fn expand_or_pat(self) -> SmallVec<[Self; 1]> {
211+
match self {
212+
PatOrWild::Pat(pat) if pat.is_or_pat() => {
213+
pat.iter_fields().map(|ipat| PatOrWild::Pat(&ipat.pat)).collect()
214+
}
215+
_ => smallvec![self],
216+
}
217+
}
218+
219+
/// Recursively expand this (possibly-nested) or-pattern into its alternatives.
194220
pub(crate) fn flatten_or_pat(self) -> SmallVec<[Self; 1]> {
195221
match self {
196222
PatOrWild::Pat(pat) if pat.is_or_pat() => pat

compiler/rustc_pattern_analysis/src/rustc.rs

+28
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT};
2020
use crate::constructor::{
2121
IntRange, MaybeInfiniteInt, OpaqueId, RangeEnd, Slice, SliceKind, VariantVisibility,
2222
};
23+
use crate::lints::lint_nonexhaustive_missing_variants;
24+
use crate::pat_column::PatternColumn;
25+
use crate::usefulness::{compute_match_usefulness, PlaceValidity};
2326
use crate::{errors, Captures, PatCx, PrivateUninhabitedField};
2427

2528
use crate::constructor::Constructor::*;
@@ -29,6 +32,8 @@ pub type Constructor<'p, 'tcx> = crate::constructor::Constructor<RustcPatCtxt<'p
2932
pub type ConstructorSet<'p, 'tcx> = crate::constructor::ConstructorSet<RustcPatCtxt<'p, 'tcx>>;
3033
pub type DeconstructedPat<'p, 'tcx> = crate::pat::DeconstructedPat<RustcPatCtxt<'p, 'tcx>>;
3134
pub type MatchArm<'p, 'tcx> = crate::MatchArm<'p, RustcPatCtxt<'p, 'tcx>>;
35+
pub type RedundancyExplanation<'p, 'tcx> =
36+
crate::usefulness::RedundancyExplanation<'p, RustcPatCtxt<'p, 'tcx>>;
3237
pub type Usefulness<'p, 'tcx> = crate::usefulness::Usefulness<'p, RustcPatCtxt<'p, 'tcx>>;
3338
pub type UsefulnessReport<'p, 'tcx> =
3439
crate::usefulness::UsefulnessReport<'p, RustcPatCtxt<'p, 'tcx>>;
@@ -1052,3 +1057,26 @@ fn expand_or_pat<'p, 'tcx>(pat: &'p Pat<'tcx>) -> Vec<&'p Pat<'tcx>> {
10521057
expand(pat, &mut pats);
10531058
pats
10541059
}
1060+
1061+
/// The entrypoint for this crate. Computes whether a match is exhaustive and which of its arms are
1062+
/// useful, and runs some lints.
1063+
pub fn analyze_match<'p, 'tcx>(
1064+
tycx: &RustcPatCtxt<'p, 'tcx>,
1065+
arms: &[MatchArm<'p, 'tcx>],
1066+
scrut_ty: Ty<'tcx>,
1067+
pattern_complexity_limit: Option<usize>,
1068+
) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
1069+
let scrut_ty = tycx.reveal_opaque_ty(scrut_ty);
1070+
let scrut_validity = PlaceValidity::from_bool(tycx.known_valid_scrutinee);
1071+
let report =
1072+
compute_match_usefulness(tycx, arms, scrut_ty, scrut_validity, pattern_complexity_limit)?;
1073+
1074+
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
1075+
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
1076+
if tycx.refutable && report.non_exhaustiveness_witnesses.is_empty() {
1077+
let pat_column = PatternColumn::new(arms);
1078+
lint_nonexhaustive_missing_variants(tycx, arms, &pat_column, scrut_ty)?;
1079+
}
1080+
1081+
Ok(report)
1082+
}

0 commit comments

Comments
 (0)