about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_error_messages/locales/en-US/mir_build.ftl36
-rw-r--r--compiler/rustc_mir_build/src/errors.rs164
-rw-r--r--compiler/rustc_mir_build/src/lib.rs1
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/check_match.rs207
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/usefulness.rs59
5 files changed, 278 insertions, 189 deletions
diff --git a/compiler/rustc_error_messages/locales/en-US/mir_build.ftl b/compiler/rustc_error_messages/locales/en-US/mir_build.ftl
index 9f10dc4b634..b277942cdcc 100644
--- a/compiler/rustc_error_messages/locales/en-US/mir_build.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/mir_build.ftl
@@ -326,11 +326,39 @@ mir_build_overlapping_range_endpoints = multiple patterns overlap on their endpo
 mir_build_overlapping_range = this range overlaps on `{$range}`...
 
 mir_build_non_exhaustive_omitted_pattern = some variants are not matched explicitly
-    .label = {$count ->
+    .help = ensure that all variants are matched explicitly by adding the suggested match arms
+    .note = the matched value is of type `{$scrut_ty}` and the `non_exhaustive_omitted_patterns` attribute was found
+
+mir_build_uncovered = {$count ->
         [1] pattern `{$witness_1}`
         [2] patterns `{$witness_1}` and `{$witness_2}`
         [3] patterns `{$witness_1}`, `{$witness_2}` and `{$witness_3}`
-        *[other] patterns `{$witness_1}`, `{$witness_2}`, `{$witness_3}` and more
+        *[other] patterns `{$witness_1}`, `{$witness_2}`, `{$witness_3}` and {$remainder} more
     } not covered
-    .help = ensure that all variants are matched explicitly by adding the suggested match arms
-    .note = the matched value is of type `{$scrut_ty}` and the `non_exhaustive_omitted_patterns` attribute was found
+
+mir_build_pattern_not_covered = refutable pattern in {$origin}
+    .pattern_ty = the matched value is of type `{$pattern_ty}`
+
+mir_build_inform_irrefutable = `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
+
+mir_build_more_information = for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
+
+mir_build_res_defined_here = {$res} defined here
+
+mir_build_adt_defined_here = `{$ty}` defined here
+
+mir_build_variant_defined_here = not covered
+
+mir_build_interpreted_as_const = introduce a variable instead
+
+mir_build_confused = missing patterns are not covered because `{$variable}` is interpreted as {$article} {$res} pattern, not a new variable
+
+mir_build_suggest_if_let = you might want to use `if let` to ignore the {$count ->
+        [one] variant that isn't
+        *[other] variants that aren't
+    } matched
+
+mir_build_suggest_let_else = alternatively, you might want to use `let else` to handle the {$count ->
+        [one] variant that isn't
+        *[other] variants that aren't
+    } matched
diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs
index e15da5bb9ce..a3c58c31654 100644
--- a/compiler/rustc_mir_build/src/errors.rs
+++ b/compiler/rustc_mir_build/src/errors.rs
@@ -1,8 +1,11 @@
+use crate::thir::pattern::deconstruct_pat::DeconstructedPat;
 use crate::thir::pattern::MatchCheckCtxt;
 use rustc_errors::Handler;
 use rustc_errors::{
-    error_code, Applicability, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, MultiSpan,
+    error_code, AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
+    IntoDiagnostic, MultiSpan, SubdiagnosticMessage,
 };
+use rustc_hir::def::Res;
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
 use rustc_middle::thir::Pat;
 use rustc_middle::ty::{self, Ty};
@@ -677,7 +680,6 @@ pub struct OverlappingRangeEndpoints<'tcx> {
     pub overlap: Overlap<'tcx>,
 }
 
-#[derive(Debug)]
 #[derive(Subdiagnostic)]
 #[label(mir_build_overlapping_range)]
 pub struct Overlap<'tcx> {
@@ -692,10 +694,158 @@ pub struct Overlap<'tcx> {
 #[note]
 pub(crate) struct NonExhaustiveOmittedPattern<'tcx> {
     pub scrut_ty: Ty<'tcx>,
-    #[label]
-    pub uncovered: Span,
+    #[subdiagnostic]
+    pub uncovered: Uncovered<'tcx>,
+}
+
+#[derive(Subdiagnostic)]
+#[label(mir_build_uncovered)]
+pub(crate) struct Uncovered<'tcx> {
+    #[primary_span]
+    span: Span,
+    count: usize,
+    witness_1: Pat<'tcx>,
+    witness_2: Pat<'tcx>,
+    witness_3: Pat<'tcx>,
+    remainder: usize,
+}
+
+impl<'tcx> Uncovered<'tcx> {
+    pub fn new<'p>(
+        span: Span,
+        cx: &MatchCheckCtxt<'p, 'tcx>,
+        witnesses: Vec<DeconstructedPat<'p, 'tcx>>,
+    ) -> Self {
+        let witness_1 = witnesses.get(0).unwrap().to_pat(cx);
+        Self {
+            span,
+            count: witnesses.len(),
+            // Substitute dummy values if witnesses is smaller than 3. These will never be read.
+            witness_2: witnesses.get(1).map(|w| w.to_pat(cx)).unwrap_or_else(|| witness_1.clone()),
+            witness_3: witnesses.get(2).map(|w| w.to_pat(cx)).unwrap_or_else(|| witness_1.clone()),
+            witness_1,
+            remainder: witnesses.len().saturating_sub(3),
+        }
+    }
+}
+
+#[derive(Diagnostic)]
+#[diag(mir_build_pattern_not_covered, code = "E0005")]
+pub(crate) struct PatternNotCovered<'s, 'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub origin: &'s str,
+    #[subdiagnostic]
+    pub uncovered: Uncovered<'tcx>,
+    #[subdiagnostic]
+    pub inform: Option<Inform>,
+    #[subdiagnostic]
+    pub interpreted_as_const: Option<InterpretedAsConst>,
+    #[subdiagnostic]
+    pub adt_defined_here: Option<AdtDefinedHere<'tcx>>,
+    #[note(pattern_ty)]
+    pub _p: (),
+    pub pattern_ty: Ty<'tcx>,
+    #[subdiagnostic]
+    pub if_let_suggestion: Option<SuggestIfLet>,
+    #[subdiagnostic]
+    pub let_else_suggestion: Option<SuggestLetElse>,
+    #[subdiagnostic]
+    pub res_defined_here: Option<ResDefinedHere>,
+}
+
+#[derive(Subdiagnostic)]
+#[note(mir_build_inform_irrefutable)]
+#[note(mir_build_more_information)]
+pub struct Inform;
+
+pub struct AdtDefinedHere<'tcx> {
+    pub adt_def_span: Span,
+    pub ty: Ty<'tcx>,
+    pub variants: Vec<Variant>,
+}
+
+pub struct Variant {
+    pub span: Span,
+}
+
+impl<'tcx> AddToDiagnostic for AdtDefinedHere<'tcx> {
+    fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
+    where
+        F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
+    {
+        diag.set_arg("ty", self.ty);
+        let mut spans = MultiSpan::from(self.adt_def_span);
+
+        for Variant { span } in self.variants {
+            spans.push_span_label(span, rustc_errors::fluent::mir_build_variant_defined_here);
+        }
+
+        diag.span_note(spans, rustc_errors::fluent::mir_build_adt_defined_here);
+    }
+}
+
+#[derive(Subdiagnostic)]
+#[label(mir_build_res_defined_here)]
+pub struct ResDefinedHere {
+    #[primary_span]
+    pub def_span: Span,
+    pub res: Res,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(
+    mir_build_interpreted_as_const,
+    code = "{variable}_var",
+    applicability = "maybe-incorrect"
+)]
+#[label(mir_build_confused)]
+pub struct InterpretedAsConst {
+    #[primary_span]
+    pub span: Span,
+    pub article: &'static str,
+    pub variable: String,
+    pub res: Res,
+}
+
+#[derive(Subdiagnostic)]
+pub enum SuggestIfLet {
+    #[multipart_suggestion(mir_build_suggest_if_let, applicability = "has-placeholders")]
+    None {
+        #[suggestion_part(code = "if ")]
+        start_span: Span,
+        #[suggestion_part(code = " {{ todo!() }}")]
+        semi_span: Span,
+        count: usize,
+    },
+    #[multipart_suggestion(mir_build_suggest_if_let, applicability = "has-placeholders")]
+    One {
+        #[suggestion_part(code = "let {binding} = if ")]
+        start_span: Span,
+        #[suggestion_part(code = " {{ {binding} }} else {{ todo!() }}")]
+        end_span: Span,
+        binding: Ident,
+        count: usize,
+    },
+    #[multipart_suggestion(mir_build_suggest_if_let, applicability = "has-placeholders")]
+    More {
+        #[suggestion_part(code = "let ({bindings}) = if ")]
+        start_span: Span,
+        #[suggestion_part(code = " {{ ({bindings}) }} else {{ todo!() }}")]
+        end_span: Span,
+        bindings: String,
+        count: usize,
+    },
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(
+    mir_build_suggest_let_else,
+    code = " else {{ todo!() }}",
+    applicability = "has-placeholders"
+)]
+pub struct SuggestLetElse {
+    #[primary_span]
+    pub end_span: Span,
     pub count: usize,
-    pub witness_1: Pat<'tcx>,
-    pub witness_2: Pat<'tcx>,
-    pub witness_3: Pat<'tcx>,
 }
diff --git a/compiler/rustc_mir_build/src/lib.rs b/compiler/rustc_mir_build/src/lib.rs
index 2b05e92fdcf..fb7ae6f1d24 100644
--- a/compiler/rustc_mir_build/src/lib.rs
+++ b/compiler/rustc_mir_build/src/lib.rs
@@ -10,6 +10,7 @@
 #![feature(let_chains)]
 #![feature(min_specialization)]
 #![feature(once_cell)]
+#![feature(try_blocks)]
 #![recursion_limit = "256"]
 
 #[macro_use]
diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
index e7ee0d9e908..422c2ff3ede 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
@@ -1,3 +1,8 @@
+//#![allow(unused_imports, unused_variables)]
+
+//#![warn(rustc::untranslatable_diagnostic)]
+//#![warn(rustc::diagnostic_outside_of_impl)]
+
 use super::deconstruct_pat::{Constructor, DeconstructedPat};
 use super::usefulness::{
     compute_match_usefulness, MatchArm, MatchCheckCtxt, Reachability, UsefulnessReport,
@@ -9,8 +14,7 @@ use crate::errors::*;
 use rustc_arena::TypedArena;
 use rustc_ast::Mutability;
 use rustc_errors::{
-    pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
-    MultiSpan,
+    struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan,
 };
 use rustc_hir as hir;
 use rustc_hir::def::*;
@@ -378,8 +382,8 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
 
         let pattern = self.lower_pattern(&mut cx, pat, &mut false);
         let pattern_ty = pattern.ty();
-        let arms = vec![MatchArm { pat: pattern, hir_id: pat.hir_id, has_guard: false }];
-        let report = compute_match_usefulness(&cx, &arms, pat.hir_id, pattern_ty);
+        let arm = MatchArm { pat: pattern, hir_id: pat.hir_id, has_guard: false };
+        let report = compute_match_usefulness(&cx, &[arm], pat.hir_id, pattern_ty);
 
         // Note: we ignore whether the pattern is unreachable (i.e. whether the type is empty). We
         // only care about exhaustiveness here.
@@ -390,145 +394,82 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
             return;
         }
 
-        let joined_patterns = joined_uncovered_patterns(&cx, &witnesses);
-
-        let mut bindings = vec![];
-
-        let mut err = struct_span_err!(
-            self.tcx.sess,
-            pat.span,
-            E0005,
-            "refutable pattern in {}: {} not covered",
-            origin,
-            joined_patterns
-        );
-        let suggest_if_let = match &pat.kind {
-            hir::PatKind::Path(hir::QPath::Resolved(None, path))
-                if path.segments.len() == 1 && path.segments[0].args.is_none() =>
+        let (inform, interpreted_as_const, res_defined_here, if_let_suggestion, let_else_suggestion) =
+            if let hir::PatKind::Path(hir::QPath::Resolved(
+                None,
+                hir::Path {
+                    segments: &[hir::PathSegment { args: None, res, ident, .. }],
+                    ..
+                },
+            )) = &pat.kind
             {
-                const_not_var(&mut err, cx.tcx, pat, path);
-                false
-            }
-            _ => {
-                pat.walk(&mut |pat: &hir::Pat<'_>| {
-                    match pat.kind {
-                        hir::PatKind::Binding(_, _, ident, _) => {
-                            bindings.push(ident);
+                (
+                    None,
+                    Some(InterpretedAsConst {
+                        span: pat.span,
+                        article: res.article(),
+                        variable: ident.to_string().to_lowercase(),
+                        res,
+                    }),
+                    try {
+                        ResDefinedHere {
+                            def_span: cx.tcx.hir().res_span(res)?,
+                            res,
                         }
-                        _ => {}
+                    },
+                    None, None,
+                )
+            } else if let Some(span) = sp && self.tcx.sess.source_map().is_span_accessible(span) {
+                let mut bindings = vec![];
+                pat.walk_always(&mut |pat: &hir::Pat<'_>| {
+                    if let hir::PatKind::Binding(_, _, ident, _) = pat.kind {
+                        bindings.push(ident);
                     }
-                    true
                 });
-
-                err.span_label(pat.span, pattern_not_covered_label(&witnesses, &joined_patterns));
-                true
-            }
-        };
-
-        if let (Some(span), true) = (sp, suggest_if_let) {
-            err.note(
-                "`let` bindings require an \"irrefutable pattern\", like a `struct` or \
-                 an `enum` with only one variant",
-            );
-            if self.tcx.sess.source_map().is_span_accessible(span) {
                 let semi_span = span.shrink_to_hi().with_lo(span.hi() - BytePos(1));
                 let start_span = span.shrink_to_lo();
                 let end_span = semi_span.shrink_to_lo();
-                err.multipart_suggestion(
-                    &format!(
-                        "you might want to use `if let` to ignore the variant{} that {} matched",
-                        pluralize!(witnesses.len()),
-                        match witnesses.len() {
-                            1 => "isn't",
-                            _ => "aren't",
-                        },
-                    ),
-                    vec![
-                        match &bindings[..] {
-                            [] => (start_span, "if ".to_string()),
-                            [binding] => (start_span, format!("let {} = if ", binding)),
-                            bindings => (
-                                start_span,
-                                format!(
-                                    "let ({}) = if ",
-                                    bindings
-                                        .iter()
-                                        .map(|ident| ident.to_string())
-                                        .collect::<Vec<_>>()
-                                        .join(", ")
-                                ),
-                            ),
-                        },
-                        match &bindings[..] {
-                            [] => (semi_span, " { todo!() }".to_string()),
-                            [binding] => {
-                                (end_span, format!(" {{ {} }} else {{ todo!() }}", binding))
-                            }
-                            bindings => (
-                                end_span,
-                                format!(
-                                    " {{ ({}) }} else {{ todo!() }}",
-                                    bindings
-                                        .iter()
-                                        .map(|ident| ident.to_string())
-                                        .collect::<Vec<_>>()
-                                        .join(", ")
-                                ),
-                            ),
-                        },
-                    ],
-                    Applicability::HasPlaceholders,
-                );
-                if !bindings.is_empty() {
-                    err.span_suggestion_verbose(
-                        semi_span.shrink_to_lo(),
-                        &format!(
-                            "alternatively, you might want to use \
-                             let else to handle the variant{} that {} matched",
-                            pluralize!(witnesses.len()),
-                            match witnesses.len() {
-                                1 => "isn't",
-                                _ => "aren't",
-                            },
-                        ),
-                        " else { todo!() }",
-                        Applicability::HasPlaceholders,
-                    );
-                }
-            }
-            err.note(
-                "for more information, visit \
-                 https://doc.rust-lang.org/book/ch18-02-refutability.html",
-            );
-        }
-
-        adt_defined_here(&cx, &mut err, pattern_ty, &witnesses);
-        err.note(&format!("the matched value is of type `{}`", pattern_ty));
-        err.emit();
-    }
-}
+                let count = witnesses.len();
+                let if_let = match *bindings {
+                    [] =>  SuggestIfLet::None{start_span, semi_span, count},
+                    [binding] => SuggestIfLet::One{start_span, end_span, count, binding },
+                    _ => SuggestIfLet::More{start_span, end_span, count, bindings: bindings
+                        .iter()
+                        .map(|ident| ident.to_string())
+                        .collect::<Vec<_>>()
+                        .join(", ")},
+                };
+                let let_else = if bindings.is_empty() {None} else{Some( SuggestLetElse{end_span, count })};
+                (sp.map(|_|Inform), None, None, Some(if_let), let_else)
+            } else{
+                (sp.map(|_|Inform), None, None,  None, None)
+            };
 
-/// A path pattern was interpreted as a constant, not a new variable.
-/// This caused an irrefutable match failure in e.g. `let`.
-fn const_not_var(err: &mut Diagnostic, tcx: TyCtxt<'_>, pat: &Pat<'_>, path: &hir::Path<'_>) {
-    let descr = path.res.descr();
-    err.span_label(
-        pat.span,
-        format!("interpreted as {} {} pattern, not a new variable", path.res.article(), descr,),
-    );
+        let adt_defined_here = try {
+            let ty = pattern_ty.peel_refs();
+            let ty::Adt(def, _) = ty.kind() else { None? };
+            let adt_def_span = cx.tcx.hir().get_if_local(def.did())?.ident()?.span;
+            let mut variants = vec![];
 
-    err.span_suggestion(
-        pat.span,
-        "introduce a variable instead",
-        format!("{}_var", path.segments[0].ident).to_lowercase(),
-        // Cannot use `MachineApplicable` as it's not really *always* correct
-        // because there may be such an identifier in scope or the user maybe
-        // really wanted to match against the constant. This is quite unlikely however.
-        Applicability::MaybeIncorrect,
-    );
+            for span in maybe_point_at_variant(&cx, *def, witnesses.iter().take(5)) {
+                variants.push(Variant { span });
+            }
+            AdtDefinedHere { adt_def_span, ty, variants }
+        };
 
-    if let Some(span) = tcx.hir().res_span(path.res) {
-        err.span_label(span, format!("{} defined here", descr));
+        self.tcx.sess.emit_err(PatternNotCovered {
+            span: pat.span,
+            origin,
+            uncovered: Uncovered::new(pat.span, &cx, witnesses),
+            inform,
+            interpreted_as_const,
+            _p: (),
+            pattern_ty,
+            if_let_suggestion,
+            let_else_suggestion,
+            res_defined_here,
+            adt_defined_here,
+        });
     }
 }
 
diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
index 37aed5bd14d..be66d0d4765 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
@@ -292,7 +292,7 @@
 use self::ArmType::*;
 use self::Usefulness::*;
 use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard};
-use crate::errors::NonExhaustiveOmittedPattern;
+use crate::errors::{NonExhaustiveOmittedPattern, Uncovered};
 
 use rustc_data_structures::captures::Captures;
 
@@ -742,49 +742,6 @@ impl<'p, 'tcx> Witness<'p, 'tcx> {
     }
 }
 
-/// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
-/// is not exhaustive enough.
-///
-/// NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`.
-fn lint_non_exhaustive_omitted_patterns<'p, 'tcx>(
-    cx: &MatchCheckCtxt<'p, 'tcx>,
-    scrut_ty: Ty<'tcx>,
-    sp: Span,
-    hir_id: HirId,
-    witnesses: Vec<DeconstructedPat<'p, 'tcx>>,
-) {
-    let witness_1 = witnesses.get(0).unwrap().to_pat(cx);
-
-    cx.tcx.emit_spanned_lint(
-        NON_EXHAUSTIVE_OMITTED_PATTERNS,
-        hir_id,
-        sp,
-        NonExhaustiveOmittedPattern {
-            scrut_ty,
-            uncovered: sp,
-            count: witnesses.len(),
-            // Substitute dummy values if witnesses is smaller than 3.
-            witness_2: witnesses.get(1).map(|w| w.to_pat(cx)).unwrap_or_else(|| witness_1.clone()),
-            witness_3: witnesses.get(2).map(|w| w.to_pat(cx)).unwrap_or_else(|| witness_1.clone()),
-            witness_1,
-        },
-    );
-    /*
-    cx.tcx.struct_span_lint_hir(NON_EXHAUSTIVE_OMITTED_PATTERNS, hir_id, sp, "some variants are not matched explicitly", |lint| {
-        let joined_patterns = joined_uncovered_patterns(cx, &witnesses);
-        lint.span_label(sp, pattern_not_covered_label(&witnesses, &joined_patterns));
-        lint.help(
-            "ensure that all variants are matched explicitly by adding the suggested match arms",
-        );
-        lint.note(&format!(
-            "the matched value is of type `{}` and the `non_exhaustive_omitted_patterns` attribute was found",
-            scrut_ty,
-        ));
-        lint
-    });
-    */
-}
-
 /// Algorithm from <http://moscova.inria.fr/~maranget/papers/warn/index.html>.
 /// The algorithm from the paper has been modified to correctly handle empty
 /// types. The changes are:
@@ -930,7 +887,19 @@ fn is_useful<'p, 'tcx>(
                         .collect::<Vec<_>>()
                 };
 
-                lint_non_exhaustive_omitted_patterns(pcx.cx, pcx.ty, pcx.span, hir_id, patterns);
+                // Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
+                // is not exhaustive enough.
+                //
+                // NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`.
+                cx.tcx.emit_spanned_lint(
+                    NON_EXHAUSTIVE_OMITTED_PATTERNS,
+                    hir_id,
+                    pcx.span,
+                    NonExhaustiveOmittedPattern {
+                        scrut_ty: pcx.ty,
+                        uncovered: Uncovered::new(pcx.span, pcx.cx, patterns),
+                    },
+                );
             }
 
             ret.extend(usefulness);