blob: eb3698957157be3326d4f47958673293461407c9 [file] [log] [blame]
use super::_match::{MatchCheckCtxt, Matrix, expand_pattern, is_useful};
use super::_match::Usefulness::*;
use super::_match::WitnessPreference::*;
use super::{Pattern, PatternContext, PatternError, PatternKind};
use rustc::middle::borrowck::SignalledError;
use rustc::session::Session;
use rustc::ty::{self, Ty, TyCtxt};
use rustc::ty::subst::{InternalSubsts, SubstsRef};
use rustc::lint;
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc::hir::def::*;
use rustc::hir::def_id::DefId;
use rustc::hir::intravisit::{self, Visitor, NestedVisitorMap};
use rustc::hir::ptr::P;
use rustc::hir::{self, Pat, PatKind};
use smallvec::smallvec;
use std::slice;
use syntax_pos::{Span, DUMMY_SP, MultiSpan};
crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) -> SignalledError {
let body_id = if let Some(id) = tcx.hir().as_local_hir_id(def_id) {
tcx.hir().body_owned_by(id)
} else {
return SignalledError::NoErrorsSeen;
};
let mut visitor = MatchVisitor {
tcx,
tables: tcx.body_tables(body_id),
param_env: tcx.param_env(def_id),
identity_substs: InternalSubsts::identity_for_item(tcx, def_id),
signalled_error: SignalledError::NoErrorsSeen,
};
visitor.visit_body(tcx.hir().body(body_id));
visitor.signalled_error
}
fn create_e0004(sess: &Session, sp: Span, error_message: String) -> DiagnosticBuilder<'_> {
struct_span_err!(sess, sp, E0004, "{}", &error_message)
}
struct MatchVisitor<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
tables: &'a ty::TypeckTables<'tcx>,
param_env: ty::ParamEnv<'tcx>,
identity_substs: SubstsRef<'tcx>,
signalled_error: SignalledError,
}
impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, 'tcx> {
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, ex: &'tcx hir::Expr) {
intravisit::walk_expr(self, ex);
if let hir::ExprKind::Match(ref scrut, ref arms, source) = ex.node {
self.check_match(scrut, arms, source);
}
}
fn visit_local(&mut self, loc: &'tcx hir::Local) {
intravisit::walk_local(self, loc);
self.check_irrefutable(&loc.pat, match loc.source {
hir::LocalSource::Normal => "local binding",
hir::LocalSource::ForLoopDesugar => "`for` loop binding",
hir::LocalSource::AsyncFn => "async fn binding",
hir::LocalSource::AwaitDesugar => "`await` future binding",
});
// Check legality of move bindings and `@` patterns.
self.check_patterns(false, slice::from_ref(&loc.pat));
}
fn visit_body(&mut self, body: &'tcx hir::Body) {
intravisit::walk_body(self, body);
for param in &body.params {
self.check_irrefutable(&param.pat, "function argument");
self.check_patterns(false, slice::from_ref(&param.pat));
}
}
}
impl PatternContext<'_, '_> {
fn report_inlining_errors(&self, pat_span: Span) {
for error in &self.errors {
match *error {
PatternError::StaticInPattern(span) => {
self.span_e0158(span, "statics cannot be referenced in patterns")
}
PatternError::AssocConstInPattern(span) => {
self.span_e0158(span, "associated consts cannot be referenced in patterns")
}
PatternError::FloatBug => {
// FIXME(#31407) this is only necessary because float parsing is buggy
::rustc::mir::interpret::struct_error(
self.tcx.at(pat_span),
"could not evaluate float literal (see issue #31407)",
).emit();
}
PatternError::NonConstPath(span) => {
::rustc::mir::interpret::struct_error(
self.tcx.at(span),
"runtime values cannot be referenced in patterns",
).emit();
}
}
}
}
fn span_e0158(&self, span: Span, text: &str) {
span_err!(self.tcx.sess, span, E0158, "{}", text)
}
}
impl<'tcx> MatchVisitor<'_, 'tcx> {
fn check_patterns(&mut self, has_guard: bool, pats: &[P<Pat>]) {
check_legality_of_move_bindings(self, has_guard, pats);
for pat in pats {
check_legality_of_bindings_in_at_patterns(self, pat);
}
}
fn check_match(
&mut self,
scrut: &hir::Expr,
arms: &'tcx [hir::Arm],
source: hir::MatchSource
) {
for arm in arms {
// First, check legality of move bindings.
self.check_patterns(arm.guard.is_some(), &arm.top_pats_hack());
// Second, if there is a guard on each arm, make sure it isn't
// assigning or borrowing anything mutably.
if arm.guard.is_some() {
self.signalled_error = SignalledError::SawSomeError;
}
// Third, perform some lints.
check_for_bindings_named_same_as_variants(self, &arm.pat);
}
let module = self.tcx.hir().get_module_parent(scrut.hir_id);
MatchCheckCtxt::create_and_enter(self.tcx, self.param_env, module, |ref mut cx| {
let mut have_errors = false;
let inlined_arms : Vec<(Vec<_>, _)> = arms.iter().map(|arm| (
arm.top_pats_hack().iter().map(|pat| {
let mut patcx = PatternContext::new(self.tcx,
self.param_env.and(self.identity_substs),
self.tables);
patcx.include_lint_checks();
let pattern = expand_pattern(cx, patcx.lower_pattern(&pat));
if !patcx.errors.is_empty() {
patcx.report_inlining_errors(pat.span);
have_errors = true;
}
(pattern, &**pat)
}).collect(),
arm.guard.as_ref().map(|g| match g {
hir::Guard::If(ref e) => &**e,
})
)).collect();
// Bail out early if inlining failed.
if have_errors {
return;
}
// Fourth, check for unreachable arms.
check_arms(cx, &inlined_arms, source);
// Then, if the match has no arms, check whether the scrutinee
// is uninhabited.
let pat_ty = self.tables.node_type(scrut.hir_id);
let module = self.tcx.hir().get_module_parent(scrut.hir_id);
let mut def_span = None;
let mut missing_variants = vec![];
if inlined_arms.is_empty() {
let scrutinee_is_uninhabited = if self.tcx.features().exhaustive_patterns {
self.tcx.is_ty_uninhabited_from(module, pat_ty)
} else {
match pat_ty.sty {
ty::Never => true,
ty::Adt(def, _) => {
def_span = self.tcx.hir().span_if_local(def.did);
if def.variants.len() < 4 && !def.variants.is_empty() {
// keep around to point at the definition of non-covered variants
missing_variants = def.variants.iter()
.map(|variant| variant.ident)
.collect();
}
let is_non_exhaustive_and_non_local =
def.is_variant_list_non_exhaustive() && !def.did.is_local();
!(is_non_exhaustive_and_non_local) && def.variants.is_empty()
},
_ => false
}
};
if !scrutinee_is_uninhabited {
// We know the type is inhabited, so this must be wrong
let mut err = create_e0004(
self.tcx.sess,
scrut.span,
format!("non-exhaustive patterns: {}", match missing_variants.len() {
0 => format!("type `{}` is non-empty", pat_ty),
1 => format!(
"pattern `{}` of type `{}` is not handled",
missing_variants[0].name,
pat_ty,
),
_ => format!("multiple patterns of type `{}` are not handled", pat_ty),
}),
);
err.help("ensure that all possible cases are being handled, \
possibly by adding wildcards or more match arms");
if let Some(sp) = def_span {
err.span_label(sp, format!("`{}` defined here", pat_ty));
}
// point at the definition of non-covered enum variants
for variant in &missing_variants {
err.span_label(variant.span, "variant not covered");
}
err.emit();
}
// If the type *is* uninhabited, it's vacuously exhaustive
return;
}
let matrix: Matrix<'_, '_> = inlined_arms
.iter()
.filter(|&&(_, guard)| guard.is_none())
.flat_map(|arm| &arm.0)
.map(|pat| smallvec![pat.0])
.collect();
let scrut_ty = self.tables.node_type(scrut.hir_id);
check_exhaustive(cx, scrut_ty, scrut.span, &matrix);
})
}
fn check_irrefutable(&self, pat: &'tcx Pat, origin: &str) {
let module = self.tcx.hir().get_module_parent(pat.hir_id);
MatchCheckCtxt::create_and_enter(self.tcx, self.param_env, module, |ref mut cx| {
let mut patcx = PatternContext::new(self.tcx,
self.param_env.and(self.identity_substs),
self.tables);
patcx.include_lint_checks();
let pattern = patcx.lower_pattern(pat);
let pattern_ty = pattern.ty;
let pats: Matrix<'_, '_> = vec![smallvec![
expand_pattern(cx, pattern)
]].into_iter().collect();
let witnesses = match check_not_useful(cx, pattern_ty, &pats) {
Ok(_) => return,
Err(err) => err,
};
let joined_patterns = joined_uncovered_patterns(&witnesses);
let mut err = struct_span_err!(
self.tcx.sess, pat.span, E0005,
"refutable pattern in {}: {} not covered",
origin, joined_patterns
);
err.span_label(pat.span, match &pat.node {
PatKind::Path(hir::QPath::Resolved(None, path))
if path.segments.len() == 1 && path.segments[0].args.is_none() => {
format!("interpreted as {} {} pattern, not new variable",
path.res.article(), path.res.descr())
}
_ => pattern_not_convered_label(&witnesses, &joined_patterns),
});
adt_defined_here(cx, &mut err, pattern_ty, &witnesses);
err.emit();
});
}
}
fn check_for_bindings_named_same_as_variants(cx: &MatchVisitor<'_, '_>, pat: &Pat) {
pat.walk(|p| {
if let PatKind::Binding(_, _, ident, None) = p.node {
if let Some(&bm) = cx.tables.pat_binding_modes().get(p.hir_id) {
if bm != ty::BindByValue(hir::MutImmutable) {
// Nothing to check.
return true;
}
let pat_ty = cx.tables.pat_ty(p);
if let ty::Adt(edef, _) = pat_ty.sty {
if edef.is_enum() && edef.variants.iter().any(|variant| {
variant.ident == ident && variant.ctor_kind == CtorKind::Const
}) {
let ty_path = cx.tcx.def_path_str(edef.did);
let mut err = struct_span_warn!(cx.tcx.sess, p.span, E0170,
"pattern binding `{}` is named the same as one \
of the variants of the type `{}`",
ident, ty_path);
err.span_suggestion(
p.span,
"to match on the variant, qualify the path",
format!("{}::{}", ty_path, ident),
Applicability::MachineApplicable
);
err.emit();
}
}
} else {
cx.tcx.sess.delay_span_bug(p.span, "missing binding mode");
}
}
true
});
}
/// Checks for common cases of "catchall" patterns that may not be intended as such.
fn pat_is_catchall(pat: &Pat) -> bool {
match pat.node {
PatKind::Binding(.., None) => true,
PatKind::Binding(.., Some(ref s)) => pat_is_catchall(s),
PatKind::Ref(ref s, _) => pat_is_catchall(s),
PatKind::Tuple(ref v, _) => v.iter().all(|p| {
pat_is_catchall(&p)
}),
_ => false
}
}
// Check for unreachable patterns
fn check_arms<'tcx>(
cx: &mut MatchCheckCtxt<'_, 'tcx>,
arms: &[(Vec<(&Pattern<'tcx>, &hir::Pat)>, Option<&hir::Expr>)],
source: hir::MatchSource,
) {
let mut seen = Matrix::empty();
let mut catchall = None;
for (arm_index, &(ref pats, guard)) in arms.iter().enumerate() {
for &(pat, hir_pat) in pats {
let v = smallvec![pat];
match is_useful(cx, &seen, &v, LeaveOutWitness) {
NotUseful => {
match source {
hir::MatchSource::IfDesugar { .. } |
hir::MatchSource::WhileDesugar => bug!(),
hir::MatchSource::IfLetDesugar { .. } => {
cx.tcx.lint_hir(
lint::builtin::IRREFUTABLE_LET_PATTERNS,
hir_pat.hir_id,
pat.span,
"irrefutable if-let pattern",
);
}
hir::MatchSource::WhileLetDesugar => {
// check which arm we're on.
match arm_index {
// The arm with the user-specified pattern.
0 => {
cx.tcx.lint_hir(
lint::builtin::UNREACHABLE_PATTERNS,
hir_pat.hir_id, pat.span,
"unreachable pattern");
},
// The arm with the wildcard pattern.
1 => {
cx.tcx.lint_hir(
lint::builtin::IRREFUTABLE_LET_PATTERNS,
hir_pat.hir_id,
pat.span,
"irrefutable while-let pattern",
);
},
_ => bug!(),
}
}
hir::MatchSource::ForLoopDesugar |
hir::MatchSource::Normal => {
let mut err = cx.tcx.struct_span_lint_hir(
lint::builtin::UNREACHABLE_PATTERNS,
hir_pat.hir_id,
pat.span,
"unreachable pattern",
);
// if we had a catchall pattern, hint at that
if let Some(catchall) = catchall {
err.span_label(pat.span, "unreachable pattern");
err.span_label(catchall, "matches any value");
}
err.emit();
}
// Unreachable patterns in try and await expressions occur when one of
// the arms are an uninhabited type. Which is OK.
hir::MatchSource::AwaitDesugar |
hir::MatchSource::TryDesugar => {}
}
}
Useful => (),
UsefulWithWitness(_) => bug!()
}
if guard.is_none() {
seen.push(v);
if catchall.is_none() && pat_is_catchall(hir_pat) {
catchall = Some(pat.span);
}
}
}
}
}
fn check_not_useful(
cx: &mut MatchCheckCtxt<'_, 'tcx>,
ty: Ty<'tcx>,
matrix: &Matrix<'_, 'tcx>,
) -> Result<(), Vec<Pattern<'tcx>>> {
let wild_pattern = Pattern { ty, span: DUMMY_SP, kind: box PatternKind::Wild };
match is_useful(cx, matrix, &[&wild_pattern], ConstructWitness) {
NotUseful => Ok(()), // This is good, wildcard pattern isn't reachable.
UsefulWithWitness(pats) => Err(if pats.is_empty() {
vec![wild_pattern]
} else {
pats.into_iter().map(|w| w.single_pattern()).collect()
}),
Useful => bug!(),
}
}
fn check_exhaustive<'tcx>(
cx: &mut MatchCheckCtxt<'_, 'tcx>,
scrut_ty: Ty<'tcx>,
sp: Span,
matrix: &Matrix<'_, 'tcx>,
) {
let witnesses = match check_not_useful(cx, scrut_ty, matrix) {
Ok(_) => return,
Err(err) => err,
};
let joined_patterns = joined_uncovered_patterns(&witnesses);
let mut err = create_e0004(
cx.tcx.sess, sp,
format!("non-exhaustive patterns: {} not covered", joined_patterns),
);
err.span_label(sp, pattern_not_convered_label(&witnesses, &joined_patterns));
adt_defined_here(cx, &mut err, scrut_ty, &witnesses);
err.help(
"ensure that all possible cases are being handled, \
possibly by adding wildcards or more match arms"
)
.emit();
}
fn joined_uncovered_patterns(witnesses: &[Pattern<'_>]) -> String {
const LIMIT: usize = 3;
match witnesses {
[] => bug!(),
[witness] => format!("`{}`", witness),
[head @ .., tail] if head.len() < LIMIT => {
let head: Vec<_> = head.iter().map(<_>::to_string).collect();
format!("`{}` and `{}`", head.join("`, `"), tail)
}
_ => {
let (head, tail) = witnesses.split_at(LIMIT);
let head: Vec<_> = head.iter().map(<_>::to_string).collect();
format!("`{}` and {} more", head.join("`, `"), tail.len())
}
}
}
fn pattern_not_convered_label(witnesses: &[Pattern<'_>], joined_patterns: &str) -> String {
format!("pattern{} {} not covered", rustc_errors::pluralise!(witnesses.len()), joined_patterns)
}
/// Point at the definition of non-covered `enum` variants.
fn adt_defined_here(
cx: &MatchCheckCtxt<'_, '_>,
err: &mut DiagnosticBuilder<'_>,
ty: Ty<'_>,
witnesses: &[Pattern<'_>],
) {
let ty = ty.peel_refs();
if let ty::Adt(def, _) = ty.sty {
if let Some(sp) = cx.tcx.hir().span_if_local(def.did) {
err.span_label(sp, format!("`{}` defined here", ty));
}
if witnesses.len() < 4 {
for sp in maybe_point_at_variant(ty, &witnesses) {
err.span_label(sp, "not covered");
}
}
}
}
fn maybe_point_at_variant(ty: Ty<'_>, patterns: &[Pattern<'_>]) -> Vec<Span> {
let mut covered = vec![];
if let ty::Adt(def, _) = ty.sty {
// Don't point at variants that have already been covered due to other patterns to avoid
// visual clutter.
for pattern in patterns {
use PatternKind::{AscribeUserType, Deref, Variant, Or, Leaf};
match &*pattern.kind {
AscribeUserType { subpattern, .. } | Deref { subpattern } => {
covered.extend(maybe_point_at_variant(ty, slice::from_ref(&subpattern)));
}
Variant { adt_def, variant_index, subpatterns, .. } if adt_def.did == def.did => {
let sp = def.variants[*variant_index].ident.span;
if covered.contains(&sp) {
continue;
}
covered.push(sp);
let pats = subpatterns.iter()
.map(|field_pattern| field_pattern.pattern.clone())
.collect::<Box<[_]>>();
covered.extend(maybe_point_at_variant(ty, &pats));
}
Leaf { subpatterns } => {
let pats = subpatterns.iter()
.map(|field_pattern| field_pattern.pattern.clone())
.collect::<Box<[_]>>();
covered.extend(maybe_point_at_variant(ty, &pats));
}
Or { pats } => {
let pats = pats.iter().cloned().collect::<Box<[_]>>();
covered.extend(maybe_point_at_variant(ty, &pats));
}
_ => {}
}
}
}
covered
}
// Legality of move bindings checking
fn check_legality_of_move_bindings(
cx: &mut MatchVisitor<'_, '_>,
has_guard: bool,
pats: &[P<Pat>],
) {
let mut by_ref_span = None;
for pat in pats {
pat.each_binding(|_, hir_id, span, _path| {
if let Some(&bm) = cx.tables.pat_binding_modes().get(hir_id) {
if let ty::BindByReference(..) = bm {
by_ref_span = Some(span);
}
} else {
cx.tcx.sess.delay_span_bug(pat.span, "missing binding mode");
}
})
}
let span_vec = &mut Vec::new();
let mut check_move = |p: &Pat, sub: Option<&Pat>| {
// Check legality of moving out of the enum.
//
// `x @ Foo(..)` is legal, but `x @ Foo(y)` isn't.
if sub.map_or(false, |p| p.contains_bindings()) {
struct_span_err!(cx.tcx.sess, p.span, E0007, "cannot bind by-move with sub-bindings")
.span_label(p.span, "binds an already bound by-move value by moving it")
.emit();
} else if !has_guard && by_ref_span.is_some() {
span_vec.push(p.span);
}
};
for pat in pats {
pat.walk(|p| {
if let PatKind::Binding(.., sub) = &p.node {
if let Some(&bm) = cx.tables.pat_binding_modes().get(p.hir_id) {
if let ty::BindByValue(..) = bm {
let pat_ty = cx.tables.node_type(p.hir_id);
if !pat_ty.is_copy_modulo_regions(cx.tcx, cx.param_env, pat.span) {
check_move(p, sub.as_deref());
}
}
} else {
cx.tcx.sess.delay_span_bug(pat.span, "missing binding mode");
}
}
true
});
}
if !span_vec.is_empty() {
let mut err = struct_span_err!(
cx.tcx.sess,
MultiSpan::from_spans(span_vec.clone()),
E0009,
"cannot bind by-move and by-ref in the same pattern",
);
if let Some(by_ref_span) = by_ref_span {
err.span_label(by_ref_span, "both by-ref and by-move used");
}
for span in span_vec.iter(){
err.span_label(*span, "by-move pattern here");
}
err.emit();
}
}
/// Forbids bindings in `@` patterns. This is necessary for memory safety,
/// because of the way rvalues are handled in the borrow check. (See issue
/// #14587.)
fn check_legality_of_bindings_in_at_patterns(cx: &MatchVisitor<'_, '_>, pat: &Pat) {
AtBindingPatternVisitor { cx, bindings_allowed: true }.visit_pat(pat);
}
struct AtBindingPatternVisitor<'a, 'b, 'tcx> {
cx: &'a MatchVisitor<'b, 'tcx>,
bindings_allowed: bool
}
impl<'v> Visitor<'v> for AtBindingPatternVisitor<'_, '_, '_> {
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'v> {
NestedVisitorMap::None
}
fn visit_pat(&mut self, pat: &Pat) {
match pat.node {
PatKind::Binding(.., ref subpat) => {
if !self.bindings_allowed {
struct_span_err!(self.cx.tcx.sess, pat.span, E0303,
"pattern bindings are not allowed after an `@`")
.span_label(pat.span, "not allowed after `@`")
.emit();
}
if subpat.is_some() {
let bindings_were_allowed = self.bindings_allowed;
self.bindings_allowed = false;
intravisit::walk_pat(self, pat);
self.bindings_allowed = bindings_were_allowed;
}
}
_ => intravisit::walk_pat(self, pat),
}
}
}