blob: a21a0ee8a1b200371524873b6209c833a144e215 [file] [log] [blame]
use rustc::infer::InferCtxt;
use rustc::lint;
use rustc::mir::Field;
use rustc::traits::predicate_for_trait_def;
use rustc::traits::{self, ObligationCause, PredicateObligation};
use rustc::ty::{self, Ty, TyCtxt};
use rustc_hir as hir;
use rustc_index::vec::Idx;
use rustc_span::Span;
use std::cell::Cell;
use super::{FieldPat, Pat, PatCtxt, PatKind};
impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
/// Converts an evaluated constant to a pattern (if possible).
/// This means aggregate values (like structs and enums) are converted
/// to a pattern that matches the value (as if you'd compared via structural equality).
pub(super) fn const_to_pat(
&self,
cv: &'tcx ty::Const<'tcx>,
id: hir::HirId,
span: Span,
) -> Pat<'tcx> {
debug!("const_to_pat: cv={:#?} id={:?}", cv, id);
debug!("const_to_pat: cv.ty={:?} span={:?}", cv.ty, span);
self.tcx.infer_ctxt().enter(|infcx| {
let mut convert = ConstToPat::new(self, id, span, infcx);
convert.to_pat(cv)
})
}
}
struct ConstToPat<'a, 'tcx> {
id: hir::HirId,
span: Span,
param_env: ty::ParamEnv<'tcx>,
// This tracks if we signal some hard error for a given const value, so that
// we will not subsequently issue an irrelevant lint for the same const
// value.
saw_const_match_error: Cell<bool>,
// inference context used for checking `T: Structural` bounds.
infcx: InferCtxt<'a, 'tcx>,
include_lint_checks: bool,
}
impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
fn new(
pat_ctxt: &PatCtxt<'_, 'tcx>,
id: hir::HirId,
span: Span,
infcx: InferCtxt<'a, 'tcx>,
) -> Self {
ConstToPat {
id,
span,
infcx,
param_env: pat_ctxt.param_env,
include_lint_checks: pat_ctxt.include_lint_checks,
saw_const_match_error: Cell::new(false),
}
}
fn tcx(&self) -> TyCtxt<'tcx> {
self.infcx.tcx
}
fn search_for_structural_match_violation(
&self,
ty: Ty<'tcx>,
) -> Option<traits::NonStructuralMatchTy<'tcx>> {
traits::search_for_structural_match_violation(self.id, self.span, self.tcx(), ty)
}
fn type_marked_structural(&self, ty: Ty<'tcx>) -> bool {
traits::type_marked_structural(self.id, self.span, &self.infcx, ty)
}
fn to_pat(&mut self, cv: &'tcx ty::Const<'tcx>) -> Pat<'tcx> {
// This method is just a wrapper handling a validity check; the heavy lifting is
// performed by the recursive `recur` method, which is not meant to be
// invoked except by this method.
//
// once indirect_structural_match is a full fledged error, this
// level of indirection can be eliminated
let inlined_const_as_pat = self.recur(cv);
if self.include_lint_checks && !self.saw_const_match_error.get() {
// If we were able to successfully convert the const to some pat,
// double-check that all types in the const implement `Structural`.
let structural = self.search_for_structural_match_violation(cv.ty);
debug!(
"search_for_structural_match_violation cv.ty: {:?} returned: {:?}",
cv.ty, structural
);
if let Some(non_sm_ty) = structural {
let adt_def = match non_sm_ty {
traits::NonStructuralMatchTy::Adt(adt_def) => adt_def,
traits::NonStructuralMatchTy::Param => {
bug!("use of constant whose type is a parameter inside a pattern")
}
};
let path = self.tcx().def_path_str(adt_def.did);
let msg = format!(
"to use a constant of type `{}` in a pattern, \
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
path, path,
);
// double-check there even *is* a semantic `PartialEq` to dispatch to.
//
// (If there isn't, then we can safely issue a hard
// error, because that's never worked, due to compiler
// using `PartialEq::eq` in this scenario in the past.)
//
// Note: To fix rust-lang/rust#65466, one could lift this check
// *before* any structural-match checking, and unconditionally error
// if `PartialEq` is not implemented. However, that breaks stable
// code at the moment, because types like `for <'a> fn(&'a ())` do
// not *yet* implement `PartialEq`. So for now we leave this here.
let ty_is_partial_eq: bool = {
let partial_eq_trait_id = self.tcx().lang_items().eq_trait().unwrap();
let obligation: PredicateObligation<'_> = predicate_for_trait_def(
self.tcx(),
self.param_env,
ObligationCause::misc(self.span, self.id),
partial_eq_trait_id,
0,
cv.ty,
&[],
);
// FIXME: should this call a `predicate_must_hold` variant instead?
self.infcx.predicate_may_hold(&obligation)
};
if !ty_is_partial_eq {
// span_fatal avoids ICE from resolution of non-existent method (rare case).
self.tcx().sess.span_fatal(self.span, &msg);
} else {
self.tcx().lint_hir(
lint::builtin::INDIRECT_STRUCTURAL_MATCH,
self.id,
self.span,
&msg,
);
}
}
}
inlined_const_as_pat
}
// Recursive helper for `to_pat`; invoke that (instead of calling this directly).
fn recur(&self, cv: &'tcx ty::Const<'tcx>) -> Pat<'tcx> {
let id = self.id;
let span = self.span;
let tcx = self.tcx();
let param_env = self.param_env;
let field_pats = |vals: &[&'tcx ty::Const<'tcx>]| {
vals.iter()
.enumerate()
.map(|(idx, val)| {
let field = Field::new(idx);
FieldPat { field, pattern: self.recur(val) }
})
.collect()
};
let kind = match cv.ty.kind {
ty::Float(_) => {
tcx.lint_hir(
::rustc::lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
id,
span,
"floating-point types cannot be used in patterns",
);
PatKind::Constant { value: cv }
}
ty::Adt(adt_def, _) if adt_def.is_union() => {
// Matching on union fields is unsafe, we can't hide it in constants
self.saw_const_match_error.set(true);
tcx.sess.span_err(span, "cannot use unions in constant patterns");
PatKind::Wild
}
// keep old code until future-compat upgraded to errors.
ty::Adt(adt_def, _) if !self.type_marked_structural(cv.ty) => {
debug!("adt_def {:?} has !type_marked_structural for cv.ty: {:?}", adt_def, cv.ty);
let path = tcx.def_path_str(adt_def.did);
let msg = format!(
"to use a constant of type `{}` in a pattern, \
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
path, path,
);
self.saw_const_match_error.set(true);
tcx.sess.span_err(span, &msg);
PatKind::Wild
}
// keep old code until future-compat upgraded to errors.
ty::Ref(_, adt_ty @ ty::TyS { kind: ty::Adt(_, _), .. }, _)
if !self.type_marked_structural(adt_ty) =>
{
let adt_def =
if let ty::Adt(adt_def, _) = adt_ty.kind { adt_def } else { unreachable!() };
debug!(
"adt_def {:?} has !type_marked_structural for adt_ty: {:?}",
adt_def, adt_ty
);
// HACK(estebank): Side-step ICE #53708, but anything other than erroring here
// would be wrong. Returnging `PatKind::Wild` is not technically correct.
let path = tcx.def_path_str(adt_def.did);
let msg = format!(
"to use a constant of type `{}` in a pattern, \
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
path, path,
);
self.saw_const_match_error.set(true);
tcx.sess.span_err(span, &msg);
PatKind::Wild
}
ty::Adt(adt_def, substs) if adt_def.is_enum() => {
let destructured = tcx.destructure_const(param_env.and(cv));
PatKind::Variant {
adt_def,
substs,
variant_index: destructured.variant,
subpatterns: field_pats(destructured.fields),
}
}
ty::Adt(_, _) => {
let destructured = tcx.destructure_const(param_env.and(cv));
PatKind::Leaf { subpatterns: field_pats(destructured.fields) }
}
ty::Tuple(_) => {
let destructured = tcx.destructure_const(param_env.and(cv));
PatKind::Leaf { subpatterns: field_pats(destructured.fields) }
}
ty::Array(..) => PatKind::Array {
prefix: tcx
.destructure_const(param_env.and(cv))
.fields
.iter()
.map(|val| self.recur(val))
.collect(),
slice: None,
suffix: Vec::new(),
},
_ => PatKind::Constant { value: cv },
};
Pat { span, ty: cv.ty, kind: Box::new(kind) }
}
}