Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate rustc_mir_build's non_exhaustive_match diagnostic #108278

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions compiler/rustc_mir_build/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,8 @@ mir_build_unused_unsafe = unnecessary `unsafe` block
mir_build_unused_unsafe_enclosing_block_label = because it's nested under this `unsafe` block
mir_build_unused_unsafe_enclosing_fn_label = because it's nested under this `unsafe` fn

mir_build_non_exhaustive_patterns_type_not_empty = non-exhaustive patterns: type `{$ty}` is non-empty
mir_build_non_exhaustive_patterns_type_not_empty = non-exhaustive patterns: type `{$scrut_ty}` is non-empty
.def_note = `{$peeled_ty}` defined here
.type_note = the matched value is of type `{$ty}`
.non_exhaustive_type_note = the matched value is of type `{$ty}`, which is marked as non-exhaustive
.reference_note = references are always considered inhabited
.suggestion = ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
.help = ensure that all possible cases are being handled by adding a match arm with a wildcard pattern

Expand Down Expand Up @@ -375,8 +372,25 @@ mir_build_suggest_let_else = you might want to use `let else` to handle the {$co

mir_build_suggest_attempted_int_lit = alternatively, you could prepend the pattern with an underscore to define a new named variable; identifiers cannot begin with digits


mir_build_rustc_box_attribute_error = `#[rustc_box]` attribute used incorrectly
.attributes = no other attributes may be applied
.not_box = `#[rustc_box]` may only be applied to a `Box::new()` call
.missing_box = `#[rustc_box]` requires the `owned_box` lang item

mir_build_non_exhaustive_pattern = match is non-exhaustive

mir_build_type_note = the matched value is of type `{$scrut_ty}`

mir_build_type_note_non_exhaustive = the matched value is of type `{$scrut_ty}`, which is marked as non-exhaustive

mir_build_no_fixed_maximum_value = `{$scrut_ty}` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively

mir_build_suggest_precise_pointer_size_matching = add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `{$scrut_ty}` matching

mir_build_ref_note = references are always considered inhabited

mir_build_suggest_wildcard_arm = ensure that all possible cases are being handled by adding a wildcard arm

mir_build_suggest_single_arm = ensure that all possible cases are being handled by adding an arm to handle the `{$pat}` case

mir_build_suggest_multiple_arms = ensure that all possible cases are being handled by adding more match arms
249 changes: 222 additions & 27 deletions compiler/rustc_mir_build/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{
fluent_generated as fluent,
thir::pattern::{deconstruct_pat::DeconstructedPat, MatchCheckCtxt},
thir::pattern::{
deconstruct_pat::{Constructor, DeconstructedPat},
MatchCheckCtxt,
},
};
use rustc_errors::{
error_code, AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
Expand All @@ -9,7 +12,7 @@ use rustc_errors::{
use rustc_hir::def::Res;
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
use rustc_middle::thir::Pat;
use rustc_middle::ty::{self, Ty};
use rustc_middle::ty::{self, AdtDef, Ty};
use rustc_span::{symbol::Ident, Span};

#[derive(LintDiagnostic)]
Expand Down Expand Up @@ -352,7 +355,9 @@ pub(crate) struct NonExhaustivePatternsTypeNotEmpty<'p, 'tcx, 'm> {
pub cx: &'m MatchCheckCtxt<'p, 'tcx>,
pub expr_span: Span,
pub span: Span,
pub ty: Ty<'tcx>,
pub scrut_ty: Ty<'tcx>,
pub type_note: TypeNote<'tcx>,
pub ref_note: Option<RefNote>,
}

impl<'a> IntoDiagnostic<'a> for NonExhaustivePatternsTypeNotEmpty<'_, '_, '_> {
Expand All @@ -363,8 +368,8 @@ impl<'a> IntoDiagnostic<'a> for NonExhaustivePatternsTypeNotEmpty<'_, '_, '_> {
error_code!(E0004),
);

let peeled_ty = self.ty.peel_refs();
diag.set_arg("ty", self.ty);
let peeled_ty = self.scrut_ty.peel_refs();
diag.set_arg("scrut_ty", self.scrut_ty);
diag.set_arg("peeled_ty", peeled_ty);

if let ty::Adt(def, _) = peeled_ty.kind() {
Expand All @@ -384,23 +389,15 @@ impl<'a> IntoDiagnostic<'a> for NonExhaustivePatternsTypeNotEmpty<'_, '_, '_> {
diag.span_note(span, fluent::mir_build_def_note);
}

let is_variant_list_non_exhaustive = match self.ty.kind() {
ty::Adt(def, _) if def.is_variant_list_non_exhaustive() && !def.did().is_local() => {
true
match self.type_note {
TypeNote::MarkedExhaustive { .. } => {
diag.note(fluent::mir_build_type_note_non_exhaustive)
}
_ => false,
TypeNote::NotMarkedExhaustive { .. } => diag.note(fluent::mir_build_type_note),
};

if is_variant_list_non_exhaustive {
diag.note(fluent::mir_build_non_exhaustive_type_note);
} else {
diag.note(fluent::mir_build_type_note);
}

if let ty::Ref(_, sub_ty, _) = self.ty.kind() {
if !sub_ty.is_inhabited_from(self.cx.tcx, self.cx.module, self.cx.param_env) {
diag.note(fluent::mir_build_reference_note);
}
if self.ref_note.is_some() {
diag.note(fluent::mir_build_ref_note);
}

let mut suggestion = None;
Expand Down Expand Up @@ -760,7 +757,7 @@ impl<'tcx> Uncovered<'tcx> {
pub fn new<'p>(
span: Span,
cx: &MatchCheckCtxt<'p, 'tcx>,
witnesses: Vec<DeconstructedPat<'p, 'tcx>>,
witnesses: &[DeconstructedPat<'p, 'tcx>],
) -> Self {
let witness_1 = witnesses.get(0).unwrap().to_pat(cx);
Self {
Expand Down Expand Up @@ -805,14 +802,52 @@ pub(crate) struct PatternNotCovered<'s, 'tcx> {
#[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(crate) struct AdtDefinedHere<'tcx> {
adt_def_span: Span,
ty: Ty<'tcx>,
variants: Vec<Span>,
}

pub struct Variant {
pub span: Span,
impl<'tcx> AdtDefinedHere<'tcx> {
pub fn new<'p>(
cx: &MatchCheckCtxt<'p, 'tcx>,
ty: Ty<'tcx>,
witnesses: &[DeconstructedPat<'p, 'tcx>],
) -> Option<Self> {
fn maybe_point_at_variant<'a, 'p: 'a, 'tcx: 'a>(
cx: &MatchCheckCtxt<'p, 'tcx>,
def: AdtDef<'tcx>,
patterns: impl Iterator<Item = &'a DeconstructedPat<'p, 'tcx>>,
) -> Vec<Span> {
let mut covered = vec![];
for pattern in patterns {
if let Constructor::Variant(variant_index) = pattern.ctor() {
if let ty::Adt(this_def, _) = pattern.ty().kind() && this_def.did() != def.did() {
continue;
}
let sp = def.variant(*variant_index).ident(cx.tcx).span;
if covered.contains(&sp) {
// Don't point at variants that have already been covered due to other patterns to avoid
// visual clutter.
continue;
}
covered.push(sp);
}
covered.extend(maybe_point_at_variant(cx, def, pattern.iter_fields()));
}
covered
}

let ty = 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![];

for span in maybe_point_at_variant(&cx, *def, witnesses.iter().take(5)) {
variants.push(span);
}
Some(AdtDefinedHere { adt_def_span, ty, variants })
}
}

impl<'tcx> AddToDiagnostic for AdtDefinedHere<'tcx> {
Expand All @@ -823,7 +858,7 @@ impl<'tcx> AddToDiagnostic for AdtDefinedHere<'tcx> {
diag.set_arg("ty", self.ty);
let mut spans = MultiSpan::from(self.adt_def_span);

for Variant { span } in self.variants {
for span in self.variants {
spans.push_span_label(span, fluent::mir_build_variant_defined_here);
}

Expand Down Expand Up @@ -907,3 +942,163 @@ pub enum RustcBoxAttrReason {
#[note(mir_build_missing_box)]
MissingBox,
}

#[derive(Diagnostic)]
#[diag(mir_build_non_exhaustive_pattern, code = "E0004")]
pub(crate) struct NonExhaustivePatterns<'tcx> {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub uncovered: Uncovered<'tcx>,
#[subdiagnostic]
pub adt_defined_here: Option<AdtDefinedHere<'tcx>>,
#[subdiagnostic]
pub type_note: TypeNote<'tcx>,
#[subdiagnostic]
pub no_fixed_max_value: Option<NoFixedMaxValue<'tcx>>,
#[subdiagnostic]
pub ppsm: Option<SuggestPrecisePointerSizeMatching<'tcx>>,
#[subdiagnostic]
pub ref_note: Option<RefNote>,
#[subdiagnostic]
pub suggest_arms: ArmSuggestions<'tcx>,
}

#[derive(Subdiagnostic)]
pub enum TypeNote<'tcx> {
#[note(mir_build_type_note)]
NotMarkedExhaustive { scrut_ty: Ty<'tcx> },
#[note(mir_build_type_note_non_exhaustive)]
MarkedExhaustive { scrut_ty: Ty<'tcx> },
}

impl<'tcx> TypeNote<'tcx> {
pub fn new(scrut_ty: Ty<'tcx>) -> Self {
match scrut_ty.kind() {
ty::Adt(def, _) if def.is_variant_list_non_exhaustive() && !def.did().is_local() => {
TypeNote::MarkedExhaustive { scrut_ty }
}
_ => TypeNote::NotMarkedExhaustive { scrut_ty },
}
}
}

#[derive(Subdiagnostic)]
pub enum AddArmKind<'tcx> {
#[help(mir_build_suggest_wildcard_arm)]
Wildcard,
#[help(mir_build_suggest_single_arm)]
Single { pat: Pat<'tcx> },
#[help(mir_build_suggest_multiple_arms)]
Multiple,
}

#[derive(Subdiagnostic)]
#[note(mir_build_no_fixed_maximum_value)]
pub struct NoFixedMaxValue<'tcx> {
pub scrut_ty: Ty<'tcx>,
}

#[derive(Subdiagnostic)]
#[help(mir_build_suggest_precise_pointer_size_matching)]
pub struct SuggestPrecisePointerSizeMatching<'tcx> {
pub scrut_ty: Ty<'tcx>,
}

#[derive(Subdiagnostic)]
#[note(mir_build_ref_note)]
pub struct RefNote;

pub enum ArmSuggestions<'tcx> {
OneLiner {
suggest_msg: AddArmKind<'tcx>,
pattern: Pat<'tcx>,
span: Span,
},
MultipleLines {
span: Span,
prefix: String,
indentation: String,
postfix: String,
arm_suggestions: Vec<Pat<'tcx>>,
suggest_msg: AddArmKind<'tcx>,
},
Help {
suggest_msg: AddArmKind<'tcx>,
},
}

impl<'tcx> AddToDiagnostic for ArmSuggestions<'tcx> {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, f: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
use std::fmt::Write;

match self {
ArmSuggestions::OneLiner { suggest_msg, span, pattern } => {
let suggestion = format!(", {pattern} => {{ todo!() }}");
let suggest_msg = match suggest_msg {
AddArmKind::Wildcard => fluent::mir_build_suggest_wildcard_arm,
AddArmKind::Single { pat } => {
diag.set_arg("pat", pat);
fluent::mir_build_suggest_single_arm
}
AddArmKind::Multiple => fluent::mir_build_suggest_multiple_arms,
};
diag.span_suggestion_verbose(
span,
suggest_msg,
suggestion,
Applicability::HasPlaceholders,
);
}
ArmSuggestions::MultipleLines {
suggest_msg,
span,
prefix,
indentation,
postfix,
arm_suggestions,
} => {
let suggest_msg = match suggest_msg {
AddArmKind::Wildcard => fluent::mir_build_suggest_wildcard_arm,
AddArmKind::Single { pat } => {
diag.set_arg("pat", pat);
fluent::mir_build_suggest_single_arm
}
AddArmKind::Multiple => fluent::mir_build_suggest_multiple_arms,
};

let mut suggestion = String::new();

// Set the correct position to start writing arms
suggestion.push_str(&prefix);

let (truncate_at, need_wildcard) = match arm_suggestions.len() {
// Avoid writing a wildcard for one remaining arm
4 => (4, false),
// Otherwise, limit it at 3 arms + wildcard
n @ 0..=3 => (n, false),
_ => (3, true),
};

for pattern in arm_suggestions.iter().take(truncate_at) {
writeln!(&mut suggestion, "{indentation}{pattern} => {{ todo!() }}").unwrap();
}
if need_wildcard {
writeln!(&mut suggestion, "{indentation}_ => {{ todo!() }}").unwrap();
}
suggestion.push_str(&postfix);

diag.span_suggestion_verbose(
span,
suggest_msg,
suggestion,
Applicability::HasPlaceholders,
);
}
ArmSuggestions::Help { suggest_msg } => suggest_msg.add_to_diagnostic_with(diag, f),
}
}
}
Loading