Fix suggestions for match non-exhaustiveness

This commit is contained in:
Michael Goulet 2024-04-02 19:06:28 -04:00
parent 9d116e8e18
commit bed8b70d67
5 changed files with 54 additions and 34 deletions

View File

@ -12,7 +12,7 @@
use rustc_errors::{DiagArgValue, IntoDiagArg}; use rustc_errors::{DiagArgValue, IntoDiagArg};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::{BindingAnnotation, ByRef, RangeEnd}; use rustc_hir::{BindingAnnotation, ByRef, MatchSource, RangeEnd};
use rustc_index::newtype_index; use rustc_index::newtype_index;
use rustc_index::IndexVec; use rustc_index::IndexVec;
use rustc_middle::middle::region; use rustc_middle::middle::region;
@ -358,6 +358,7 @@ pub enum ExprKind<'tcx> {
scrutinee: ExprId, scrutinee: ExprId,
scrutinee_hir_id: hir::HirId, scrutinee_hir_id: hir::HirId,
arms: Box<[ArmId]>, arms: Box<[ArmId]>,
match_source: MatchSource,
}, },
/// A block. /// A block.
Block { Block {

View File

@ -456,8 +456,8 @@ pub enum UnusedUnsafeEnclosing {
pub(crate) struct NonExhaustivePatternsTypeNotEmpty<'p, 'tcx, 'm> { pub(crate) struct NonExhaustivePatternsTypeNotEmpty<'p, 'tcx, 'm> {
pub cx: &'m RustcPatCtxt<'p, 'tcx>, pub cx: &'m RustcPatCtxt<'p, 'tcx>,
pub expr_span: Span, pub scrut_span: Span,
pub span: Span, pub braces_span: Option<Span>,
pub ty: Ty<'tcx>, pub ty: Ty<'tcx>,
} }
@ -465,7 +465,7 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for NonExhaustivePatternsTypeNo
fn into_diag(self, dcx: &'a DiagCtxt, level: Level) -> Diag<'_, G> { fn into_diag(self, dcx: &'a DiagCtxt, level: Level) -> Diag<'_, G> {
let mut diag = let mut diag =
Diag::new(dcx, level, fluent::mir_build_non_exhaustive_patterns_type_not_empty); Diag::new(dcx, level, fluent::mir_build_non_exhaustive_patterns_type_not_empty);
diag.span(self.span); diag.span(self.scrut_span);
diag.code(E0004); diag.code(E0004);
let peeled_ty = self.ty.peel_refs(); let peeled_ty = self.ty.peel_refs();
diag.arg("ty", self.ty); diag.arg("ty", self.ty);
@ -502,26 +502,19 @@ fn into_diag(self, dcx: &'a DiagCtxt, level: Level) -> Diag<'_, G> {
} }
} }
let mut suggestion = None;
let sm = self.cx.tcx.sess.source_map(); let sm = self.cx.tcx.sess.source_map();
if self.span.eq_ctxt(self.expr_span) { if let Some(braces_span) = self.braces_span {
// Get the span for the empty match body `{}`. // Get the span for the empty match body `{}`.
let (indentation, more) = if let Some(snippet) = sm.indentation_before(self.span) { let (indentation, more) = if let Some(snippet) = sm.indentation_before(self.scrut_span)
{
(format!("\n{snippet}"), " ") (format!("\n{snippet}"), " ")
} else { } else {
(" ".to_string(), "") (" ".to_string(), "")
}; };
suggestion = Some((
self.span.shrink_to_hi().with_hi(self.expr_span.hi()),
format!(" {{{indentation}{more}_ => todo!(),{indentation}}}",),
));
}
if let Some((span, sugg)) = suggestion {
diag.span_suggestion_verbose( diag.span_suggestion_verbose(
span, braces_span,
fluent::mir_build_suggestion, fluent::mir_build_suggestion,
sugg, format!(" {{{indentation}{more}_ => todo!(),{indentation}}}"),
Applicability::HasPlaceholders, Applicability::HasPlaceholders,
); );
} else { } else {

View File

@ -716,10 +716,11 @@ fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx>
then: self.mirror_expr(then), then: self.mirror_expr(then),
else_opt: else_opt.map(|el| self.mirror_expr(el)), else_opt: else_opt.map(|el| self.mirror_expr(el)),
}, },
hir::ExprKind::Match(discr, arms, _) => ExprKind::Match { hir::ExprKind::Match(discr, arms, match_source) => ExprKind::Match {
scrutinee: self.mirror_expr(discr), scrutinee: self.mirror_expr(discr),
scrutinee_hir_id: discr.hir_id, scrutinee_hir_id: discr.hir_id,
arms: arms.iter().map(|a| self.convert_arm(a)).collect(), arms: arms.iter().map(|a| self.convert_arm(a)).collect(),
match_source,
}, },
hir::ExprKind::Loop(body, ..) => { hir::ExprKind::Loop(body, ..) => {
let block_ty = self.typeck_results().node_type(body.hir_id); let block_ty = self.typeck_results().node_type(body.hir_id);

View File

@ -144,16 +144,8 @@ fn visit_expr(&mut self, ex: &'p Expr<'tcx>) {
}); });
return; return;
} }
ExprKind::Match { scrutinee, scrutinee_hir_id, box ref arms } => { ExprKind::Match { scrutinee, scrutinee_hir_id: _, box ref arms, match_source } => {
let source = match ex.span.desugaring_kind() { self.check_match(scrutinee, arms, match_source, ex.span);
Some(DesugaringKind::ForLoop) => hir::MatchSource::ForLoopDesugar,
Some(DesugaringKind::QuestionMark) => {
hir::MatchSource::TryDesugar(scrutinee_hir_id)
}
Some(DesugaringKind::Await) => hir::MatchSource::AwaitDesugar,
_ => hir::MatchSource::Normal,
};
self.check_match(scrutinee, arms, source, ex.span);
} }
ExprKind::Let { box ref pat, expr } => { ExprKind::Let { box ref pat, expr } => {
self.check_let(pat, Some(expr), ex.span); self.check_let(pat, Some(expr), ex.span);
@ -505,8 +497,41 @@ fn check_match(
None, None,
); );
} else { } else {
// span after scrutinee, or after `.match`. That is, the braces, arms,
// and any whitespace preceding the braces.
let braces_span = match source {
hir::MatchSource::Normal => scrut
.span
.find_ancestor_in_same_ctxt(expr_span)
.map(|scrut_span| scrut_span.shrink_to_hi().with_hi(expr_span.hi())),
hir::MatchSource::Postfix => {
// This is horrendous, and we should deal with it by just
// stashing the span of the braces somewhere (like in the match source).
scrut.span.find_ancestor_in_same_ctxt(expr_span).and_then(|scrut_span| {
let sm = self.tcx.sess.source_map();
let brace_span = sm.span_extend_to_next_char(scrut_span, '{', true);
if sm.span_to_snippet(sm.next_point(brace_span)).as_deref() == Ok("{") {
let sp = brace_span.shrink_to_hi().with_hi(expr_span.hi());
// We also need to extend backwards for whitespace
sm.span_extend_prev_while(sp, |c| c.is_whitespace()).ok()
} else {
None
}
})
}
hir::MatchSource::ForLoopDesugar
| hir::MatchSource::TryDesugar(_)
| hir::MatchSource::AwaitDesugar
| hir::MatchSource::FormatArgs => None,
};
self.error = Err(report_non_exhaustive_match( self.error = Err(report_non_exhaustive_match(
&cx, self.thir, scrut.ty, scrut.span, witnesses, arms, expr_span, &cx,
self.thir,
scrut.ty,
scrut.span,
witnesses,
arms,
braces_span,
)); ));
} }
} }
@ -929,7 +954,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
sp: Span, sp: Span,
witnesses: Vec<WitnessPat<'p, 'tcx>>, witnesses: Vec<WitnessPat<'p, 'tcx>>,
arms: &[ArmId], arms: &[ArmId],
expr_span: Span, braces_span: Option<Span>,
) -> ErrorGuaranteed { ) -> ErrorGuaranteed {
let is_empty_match = arms.is_empty(); let is_empty_match = arms.is_empty();
let non_empty_enum = match scrut_ty.kind() { let non_empty_enum = match scrut_ty.kind() {
@ -941,8 +966,8 @@ fn report_non_exhaustive_match<'p, 'tcx>(
if is_empty_match && !non_empty_enum { if is_empty_match && !non_empty_enum {
return cx.tcx.dcx().emit_err(NonExhaustivePatternsTypeNotEmpty { return cx.tcx.dcx().emit_err(NonExhaustivePatternsTypeNotEmpty {
cx, cx,
expr_span, scrut_span: sp,
span: sp, braces_span,
ty: scrut_ty, ty: scrut_ty,
}); });
} }
@ -1028,7 +1053,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
let mut suggestion = None; let mut suggestion = None;
let sm = cx.tcx.sess.source_map(); let sm = cx.tcx.sess.source_map();
match arms { match arms {
[] if sp.eq_ctxt(expr_span) => { [] if let Some(braces_span) = braces_span => {
// Get the span for the empty match body `{}`. // Get the span for the empty match body `{}`.
let (indentation, more) = if let Some(snippet) = sm.indentation_before(sp) { let (indentation, more) = if let Some(snippet) = sm.indentation_before(sp) {
(format!("\n{snippet}"), " ") (format!("\n{snippet}"), " ")
@ -1036,7 +1061,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
(" ".to_string(), "") (" ".to_string(), "")
}; };
suggestion = Some(( suggestion = Some((
sp.shrink_to_hi().with_hi(expr_span.hi()), braces_span,
format!(" {{{indentation}{more}{suggested_arm},{indentation}}}",), format!(" {{{indentation}{more}{suggested_arm},{indentation}}}",),
)); ));
} }

View File

@ -18,7 +18,7 @@ LL | 1 as i32.match {};
= note: the matched value is of type `i32` = note: the matched value is of type `i32`
help: 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 as shown
| |
LL ~ 1 as i32 { LL ~ 1 as i32.match {
LL + _ => todo!(), LL + _ => todo!(),
LL ~ }; LL ~ };
| |