Auto merge of #63862 - Centril:match-cleanup, r=oli-obk
typeck: refactor patterns => `pat.rs` + make the `def_bm` algo more declarative Spurred by the relative difficulty I had in working up an explanation of how default match bindings work in https://github.com/rust-lang/rust/pull/63118#issuecomment-524161584, this PR refactors the type checking of patterns into `pat.rs`. The PR is probably best read commit-by-commit and includes various changes beyond the following, which are the most important highlights: - The algorithm for determining `def_bm` is encoded in a more declarative fashion now with important sub-steps divided into functions that make sense as logical units (and as described in the reference). This is done starting with *"extract `is_no_ref_pat`."* to *"extract `calc_default_binding_mode`"*. - Dedicated functions like `check_pat_{lit,range,ident,tuple,box,ref,slice}` are then introduced for the various kinds of patterns to make things overall more readable. - `fn check_pat_top(...)` becomes the sole entry point to type checking patterns. This will take care of initializing the default binding mode (hence: `def_bm`) to `BindByValue` and is called by all contexts that have a pattern that needs to be type checked (functions, `match`, `if let`, `let`, ...). The overall result is that the notion of `def_bm` is internal to checking patterns. - Various diagnostics are extracted to dedicated functions to disturb the flow of type checking logic less. r? @oli-obk
This commit is contained in:
commit
783469ca09
3 changed files with 1129 additions and 973 deletions
|
|
@ -1,631 +1,12 @@
|
|||
use crate::check::{FnCtxt, Expectation, Diverges, Needs};
|
||||
use crate::check::coercion::CoerceMany;
|
||||
use crate::util::nodemap::FxHashMap;
|
||||
use errors::{Applicability, DiagnosticBuilder};
|
||||
use rustc::hir::{self, PatKind, Pat, ExprKind};
|
||||
use rustc::hir::def::{Res, DefKind, CtorKind};
|
||||
use rustc::hir::pat_util::EnumerateAndAdjustIterator;
|
||||
use rustc::hir::ptr::P;
|
||||
use rustc::infer;
|
||||
use rustc::hir::{self, ExprKind};
|
||||
use rustc::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc::traits::{ObligationCause, ObligationCauseCode};
|
||||
use rustc::ty::{self, Ty, TypeFoldable};
|
||||
use rustc::ty::subst::Kind;
|
||||
use syntax::ast;
|
||||
use syntax::util::lev_distance::find_best_match_for_name;
|
||||
use rustc::ty::Ty;
|
||||
use syntax_pos::Span;
|
||||
use syntax_pos::hygiene::DesugaringKind;
|
||||
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
use std::cmp;
|
||||
|
||||
use super::report_unexpected_variant_res;
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
/// `discrim_span` argument having a `Span` indicates that this pattern is part of a match
|
||||
/// expression arm guard, and it points to the match discriminant to add context in type errors.
|
||||
/// In the following example, `discrim_span` corresponds to the `a + b` expression:
|
||||
///
|
||||
/// ```text
|
||||
/// error[E0308]: mismatched types
|
||||
/// --> src/main.rs:5:9
|
||||
/// |
|
||||
/// 4 | let temp: usize = match a + b {
|
||||
/// | ----- this expression has type `usize`
|
||||
/// 5 | Ok(num) => num,
|
||||
/// | ^^^^^^^ expected usize, found enum `std::result::Result`
|
||||
/// |
|
||||
/// = note: expected type `usize`
|
||||
/// found type `std::result::Result<_, _>`
|
||||
/// ```
|
||||
pub fn check_pat_walk(
|
||||
&self,
|
||||
pat: &'tcx hir::Pat,
|
||||
mut expected: Ty<'tcx>,
|
||||
mut def_bm: ty::BindingMode,
|
||||
discrim_span: Option<Span>,
|
||||
) {
|
||||
let tcx = self.tcx;
|
||||
|
||||
debug!("check_pat_walk(pat={:?},expected={:?},def_bm={:?})", pat, expected, def_bm);
|
||||
|
||||
let mut path_resolution = None;
|
||||
let is_non_ref_pat = match pat.node {
|
||||
PatKind::Struct(..) |
|
||||
PatKind::TupleStruct(..) |
|
||||
PatKind::Or(_) |
|
||||
PatKind::Tuple(..) |
|
||||
PatKind::Box(_) |
|
||||
PatKind::Range(..) |
|
||||
PatKind::Slice(..) => true,
|
||||
PatKind::Lit(ref lt) => {
|
||||
let ty = self.check_expr(lt);
|
||||
match ty.sty {
|
||||
ty::Ref(..) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
PatKind::Path(ref qpath) => {
|
||||
let resolution = self.resolve_ty_and_res_ufcs(qpath, pat.hir_id, pat.span);
|
||||
path_resolution = Some(resolution);
|
||||
match resolution.0 {
|
||||
Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
PatKind::Wild |
|
||||
PatKind::Binding(..) |
|
||||
PatKind::Ref(..) => false,
|
||||
};
|
||||
if is_non_ref_pat {
|
||||
debug!("pattern is non reference pattern");
|
||||
let mut exp_ty = self.resolve_type_vars_with_obligations(&expected);
|
||||
|
||||
// Peel off as many `&` or `&mut` from the discriminant as possible. For example,
|
||||
// for `match &&&mut Some(5)` the loop runs three times, aborting when it reaches
|
||||
// the `Some(5)` which is not of type Ref.
|
||||
//
|
||||
// For each ampersand peeled off, update the binding mode and push the original
|
||||
// type into the adjustments vector.
|
||||
//
|
||||
// See the examples in `ui/match-defbm*.rs`.
|
||||
let mut pat_adjustments = vec![];
|
||||
while let ty::Ref(_, inner_ty, inner_mutability) = exp_ty.sty {
|
||||
debug!("inspecting {:?}", exp_ty);
|
||||
|
||||
debug!("current discriminant is Ref, inserting implicit deref");
|
||||
// Preserve the reference type. We'll need it later during HAIR lowering.
|
||||
pat_adjustments.push(exp_ty);
|
||||
|
||||
exp_ty = inner_ty;
|
||||
def_bm = match def_bm {
|
||||
// If default binding mode is by value, make it `ref` or `ref mut`
|
||||
// (depending on whether we observe `&` or `&mut`).
|
||||
ty::BindByValue(_) =>
|
||||
ty::BindByReference(inner_mutability),
|
||||
|
||||
// Once a `ref`, always a `ref`. This is because a `& &mut` can't mutate
|
||||
// the underlying value.
|
||||
ty::BindByReference(hir::Mutability::MutImmutable) =>
|
||||
ty::BindByReference(hir::Mutability::MutImmutable),
|
||||
|
||||
// When `ref mut`, stay a `ref mut` (on `&mut`) or downgrade to `ref`
|
||||
// (on `&`).
|
||||
ty::BindByReference(hir::Mutability::MutMutable) =>
|
||||
ty::BindByReference(inner_mutability),
|
||||
};
|
||||
}
|
||||
expected = exp_ty;
|
||||
|
||||
if pat_adjustments.len() > 0 {
|
||||
debug!("default binding mode is now {:?}", def_bm);
|
||||
self.inh.tables.borrow_mut()
|
||||
.pat_adjustments_mut()
|
||||
.insert(pat.hir_id, pat_adjustments);
|
||||
}
|
||||
} else if let PatKind::Ref(..) = pat.node {
|
||||
// When you encounter a `&pat` pattern, reset to "by
|
||||
// value". This is so that `x` and `y` here are by value,
|
||||
// as they appear to be:
|
||||
//
|
||||
// ```
|
||||
// match &(&22, &44) {
|
||||
// (&x, &y) => ...
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// See issue #46688.
|
||||
def_bm = ty::BindByValue(hir::MutImmutable);
|
||||
}
|
||||
|
||||
// Lose mutability now that we know binding mode and discriminant type.
|
||||
let def_bm = def_bm;
|
||||
let expected = expected;
|
||||
|
||||
let ty = match pat.node {
|
||||
PatKind::Wild => {
|
||||
expected
|
||||
}
|
||||
PatKind::Lit(ref lt) => {
|
||||
// We've already computed the type above (when checking for a non-ref pat), so
|
||||
// avoid computing it again.
|
||||
let ty = self.node_ty(lt.hir_id);
|
||||
|
||||
// Byte string patterns behave the same way as array patterns
|
||||
// They can denote both statically and dynamically-sized byte arrays.
|
||||
let mut pat_ty = ty;
|
||||
if let hir::ExprKind::Lit(ref lt) = lt.node {
|
||||
if let ast::LitKind::ByteStr(_) = lt.node {
|
||||
let expected_ty = self.structurally_resolved_type(pat.span, expected);
|
||||
if let ty::Ref(_, r_ty, _) = expected_ty.sty {
|
||||
if let ty::Slice(_) = r_ty.sty {
|
||||
pat_ty = tcx.mk_imm_ref(tcx.lifetimes.re_static,
|
||||
tcx.mk_slice(tcx.types.u8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Somewhat surprising: in this case, the subtyping
|
||||
// relation goes the opposite way as the other
|
||||
// cases. Actually what we really want is not a subtyping
|
||||
// relation at all but rather that there exists a LUB (so
|
||||
// that they can be compared). However, in practice,
|
||||
// constants are always scalars or strings. For scalars
|
||||
// subtyping is irrelevant, and for strings `ty` is
|
||||
// type is `&'static str`, so if we say that
|
||||
//
|
||||
// &'static str <: expected
|
||||
//
|
||||
// then that's equivalent to there existing a LUB.
|
||||
if let Some(mut err) = self.demand_suptype_diag(pat.span, expected, pat_ty) {
|
||||
err.emit_unless(discrim_span
|
||||
.filter(|&s| {
|
||||
// In the case of `if`- and `while`-expressions we've already checked
|
||||
// that `scrutinee: bool`. We know that the pattern is `true`,
|
||||
// so an error here would be a duplicate and from the wrong POV.
|
||||
s.is_desugaring(DesugaringKind::CondTemporary)
|
||||
})
|
||||
.is_some());
|
||||
}
|
||||
|
||||
pat_ty
|
||||
}
|
||||
PatKind::Range(ref begin, ref end, _) => {
|
||||
let lhs_ty = self.check_expr(begin);
|
||||
let rhs_ty = self.check_expr(end);
|
||||
|
||||
// Check that both end-points are of numeric or char type.
|
||||
let numeric_or_char = |ty: Ty<'_>| {
|
||||
ty.is_numeric()
|
||||
|| ty.is_char()
|
||||
|| ty.references_error()
|
||||
};
|
||||
let lhs_compat = numeric_or_char(lhs_ty);
|
||||
let rhs_compat = numeric_or_char(rhs_ty);
|
||||
|
||||
if !lhs_compat || !rhs_compat {
|
||||
let span = if !lhs_compat && !rhs_compat {
|
||||
pat.span
|
||||
} else if !lhs_compat {
|
||||
begin.span
|
||||
} else {
|
||||
end.span
|
||||
};
|
||||
|
||||
let mut err = struct_span_err!(
|
||||
tcx.sess,
|
||||
span,
|
||||
E0029,
|
||||
"only char and numeric types are allowed in range patterns"
|
||||
);
|
||||
err.span_label(span, "ranges require char or numeric types");
|
||||
err.note(&format!("start type: {}", self.ty_to_string(lhs_ty)));
|
||||
err.note(&format!("end type: {}", self.ty_to_string(rhs_ty)));
|
||||
if tcx.sess.teach(&err.get_code().unwrap()) {
|
||||
err.note(
|
||||
"In a match expression, only numbers and characters can be matched \
|
||||
against a range. This is because the compiler checks that the range \
|
||||
is non-empty at compile-time, and is unable to evaluate arbitrary \
|
||||
comparison functions. If you want to capture values of an orderable \
|
||||
type between two end-points, you can use a guard."
|
||||
);
|
||||
}
|
||||
err.emit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Now that we know the types can be unified we find the unified type and use
|
||||
// it to type the entire expression.
|
||||
let common_type = self.resolve_vars_if_possible(&lhs_ty);
|
||||
|
||||
// Subtyping doesn't matter here, as the value is some kind of scalar.
|
||||
self.demand_eqtype_pat(pat.span, expected, lhs_ty, discrim_span);
|
||||
self.demand_eqtype_pat(pat.span, expected, rhs_ty, discrim_span);
|
||||
common_type
|
||||
}
|
||||
PatKind::Binding(ba, var_id, _, ref sub) => {
|
||||
let bm = if ba == hir::BindingAnnotation::Unannotated {
|
||||
def_bm
|
||||
} else {
|
||||
ty::BindingMode::convert(ba)
|
||||
};
|
||||
self.inh
|
||||
.tables
|
||||
.borrow_mut()
|
||||
.pat_binding_modes_mut()
|
||||
.insert(pat.hir_id, bm);
|
||||
debug!("check_pat_walk: pat.hir_id={:?} bm={:?}", pat.hir_id, bm);
|
||||
let local_ty = self.local_ty(pat.span, pat.hir_id).decl_ty;
|
||||
match bm {
|
||||
ty::BindByReference(mutbl) => {
|
||||
// If the binding is like
|
||||
// ref x | ref const x | ref mut x
|
||||
// then `x` is assigned a value of type `&M T` where M is the mutability
|
||||
// and T is the expected type.
|
||||
let region_var = self.next_region_var(infer::PatternRegion(pat.span));
|
||||
let mt = ty::TypeAndMut { ty: expected, mutbl: mutbl };
|
||||
let region_ty = tcx.mk_ref(region_var, mt);
|
||||
|
||||
// `x` is assigned a value of type `&M T`, hence `&M T <: typeof(x)` is
|
||||
// required. However, we use equality, which is stronger. See (*) for
|
||||
// an explanation.
|
||||
self.demand_eqtype_pat(pat.span, region_ty, local_ty, discrim_span);
|
||||
}
|
||||
// Otherwise, the type of x is the expected type `T`.
|
||||
ty::BindByValue(_) => {
|
||||
// As above, `T <: typeof(x)` is required, but we
|
||||
// use equality, see (*) below.
|
||||
self.demand_eqtype_pat(pat.span, expected, local_ty, discrim_span);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are multiple arms, make sure they all agree on
|
||||
// what the type of the binding `x` ought to be.
|
||||
if var_id != pat.hir_id {
|
||||
let vt = self.local_ty(pat.span, var_id).decl_ty;
|
||||
self.demand_eqtype_pat(pat.span, vt, local_ty, discrim_span);
|
||||
}
|
||||
|
||||
if let Some(ref p) = *sub {
|
||||
self.check_pat_walk(&p, expected, def_bm, discrim_span);
|
||||
}
|
||||
|
||||
local_ty
|
||||
}
|
||||
PatKind::TupleStruct(ref qpath, ref subpats, ddpos) => {
|
||||
self.check_pat_tuple_struct(
|
||||
pat,
|
||||
qpath,
|
||||
&subpats,
|
||||
ddpos,
|
||||
expected,
|
||||
def_bm,
|
||||
discrim_span,
|
||||
)
|
||||
}
|
||||
PatKind::Path(ref qpath) => {
|
||||
self.check_pat_path(pat, path_resolution.unwrap(), qpath, expected)
|
||||
}
|
||||
PatKind::Struct(ref qpath, ref fields, etc) => {
|
||||
self.check_pat_struct(pat, qpath, fields, etc, expected, def_bm, discrim_span)
|
||||
}
|
||||
PatKind::Or(ref pats) => {
|
||||
let expected_ty = self.structurally_resolved_type(pat.span, expected);
|
||||
for pat in pats {
|
||||
self.check_pat_walk(pat, expected, def_bm, discrim_span);
|
||||
}
|
||||
expected_ty
|
||||
}
|
||||
PatKind::Tuple(ref elements, ddpos) => {
|
||||
let mut expected_len = elements.len();
|
||||
if ddpos.is_some() {
|
||||
// Require known type only when `..` is present.
|
||||
if let ty::Tuple(ref tys) =
|
||||
self.structurally_resolved_type(pat.span, expected).sty {
|
||||
expected_len = tys.len();
|
||||
}
|
||||
}
|
||||
let max_len = cmp::max(expected_len, elements.len());
|
||||
|
||||
let element_tys_iter = (0..max_len).map(|_| {
|
||||
Kind::from(self.next_ty_var(
|
||||
// FIXME: `MiscVariable` for now -- obtaining the span and name information
|
||||
// from all tuple elements isn't trivial.
|
||||
TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::TypeInference,
|
||||
span: pat.span,
|
||||
},
|
||||
))
|
||||
});
|
||||
let element_tys = tcx.mk_substs(element_tys_iter);
|
||||
let pat_ty = tcx.mk_ty(ty::Tuple(element_tys));
|
||||
if let Some(mut err) = self.demand_eqtype_diag(pat.span, expected, pat_ty) {
|
||||
err.emit();
|
||||
// Walk subpatterns with an expected type of `err` in this case to silence
|
||||
// further errors being emitted when using the bindings. #50333
|
||||
let element_tys_iter = (0..max_len).map(|_| tcx.types.err);
|
||||
for (_, elem) in elements.iter().enumerate_and_adjust(max_len, ddpos) {
|
||||
self.check_pat_walk(elem, &tcx.types.err, def_bm, discrim_span);
|
||||
}
|
||||
tcx.mk_tup(element_tys_iter)
|
||||
} else {
|
||||
for (i, elem) in elements.iter().enumerate_and_adjust(max_len, ddpos) {
|
||||
self.check_pat_walk(
|
||||
elem,
|
||||
&element_tys[i].expect_ty(),
|
||||
def_bm,
|
||||
discrim_span,
|
||||
);
|
||||
}
|
||||
pat_ty
|
||||
}
|
||||
}
|
||||
PatKind::Box(ref inner) => {
|
||||
let inner_ty = self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::TypeInference,
|
||||
span: inner.span,
|
||||
});
|
||||
let uniq_ty = tcx.mk_box(inner_ty);
|
||||
|
||||
if self.check_dereferencable(pat.span, expected, &inner) {
|
||||
// Here, `demand::subtype` is good enough, but I don't
|
||||
// think any errors can be introduced by using
|
||||
// `demand::eqtype`.
|
||||
self.demand_eqtype_pat(pat.span, expected, uniq_ty, discrim_span);
|
||||
self.check_pat_walk(&inner, inner_ty, def_bm, discrim_span);
|
||||
uniq_ty
|
||||
} else {
|
||||
self.check_pat_walk(&inner, tcx.types.err, def_bm, discrim_span);
|
||||
tcx.types.err
|
||||
}
|
||||
}
|
||||
PatKind::Ref(ref inner, mutbl) => {
|
||||
let expected = self.shallow_resolve(expected);
|
||||
if self.check_dereferencable(pat.span, expected, &inner) {
|
||||
// `demand::subtype` would be good enough, but using
|
||||
// `eqtype` turns out to be equally general. See (*)
|
||||
// below for details.
|
||||
|
||||
// Take region, inner-type from expected type if we
|
||||
// can, to avoid creating needless variables. This
|
||||
// also helps with the bad interactions of the given
|
||||
// hack detailed in (*) below.
|
||||
debug!("check_pat_walk: expected={:?}", expected);
|
||||
let (rptr_ty, inner_ty) = match expected.sty {
|
||||
ty::Ref(_, r_ty, r_mutbl) if r_mutbl == mutbl => {
|
||||
(expected, r_ty)
|
||||
}
|
||||
_ => {
|
||||
let inner_ty = self.next_ty_var(
|
||||
TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::TypeInference,
|
||||
span: inner.span,
|
||||
}
|
||||
);
|
||||
let mt = ty::TypeAndMut { ty: inner_ty, mutbl: mutbl };
|
||||
let region = self.next_region_var(infer::PatternRegion(pat.span));
|
||||
let rptr_ty = tcx.mk_ref(region, mt);
|
||||
debug!("check_pat_walk: demanding {:?} = {:?}", expected, rptr_ty);
|
||||
let err = self.demand_eqtype_diag(pat.span, expected, rptr_ty);
|
||||
|
||||
// Look for a case like `fn foo(&foo: u32)` and suggest
|
||||
// `fn foo(foo: &u32)`
|
||||
if let Some(mut err) = err {
|
||||
self.borrow_pat_suggestion(&mut err, &pat, &inner, &expected);
|
||||
err.emit();
|
||||
}
|
||||
(rptr_ty, inner_ty)
|
||||
}
|
||||
};
|
||||
|
||||
self.check_pat_walk(&inner, inner_ty, def_bm, discrim_span);
|
||||
rptr_ty
|
||||
} else {
|
||||
self.check_pat_walk(&inner, tcx.types.err, def_bm, discrim_span);
|
||||
tcx.types.err
|
||||
}
|
||||
}
|
||||
PatKind::Slice(ref before, ref slice, ref after) => {
|
||||
let expected_ty = self.structurally_resolved_type(pat.span, expected);
|
||||
let (inner_ty, slice_ty) = match expected_ty.sty {
|
||||
ty::Array(inner_ty, size) => {
|
||||
if let Some(size) = size.try_eval_usize(tcx, self.param_env) {
|
||||
let min_len = before.len() as u64 + after.len() as u64;
|
||||
if slice.is_none() {
|
||||
if min_len != size {
|
||||
struct_span_err!(
|
||||
tcx.sess, pat.span, E0527,
|
||||
"pattern requires {} elements but array has {}",
|
||||
min_len, size)
|
||||
.span_label(pat.span, format!("expected {} elements", size))
|
||||
.emit();
|
||||
}
|
||||
(inner_ty, tcx.types.err)
|
||||
} else if let Some(rest) = size.checked_sub(min_len) {
|
||||
(inner_ty, tcx.mk_array(inner_ty, rest))
|
||||
} else {
|
||||
struct_span_err!(tcx.sess, pat.span, E0528,
|
||||
"pattern requires at least {} elements but array has {}",
|
||||
min_len, size)
|
||||
.span_label(pat.span,
|
||||
format!("pattern cannot match array of {} elements", size))
|
||||
.emit();
|
||||
(inner_ty, tcx.types.err)
|
||||
}
|
||||
} else {
|
||||
struct_span_err!(
|
||||
tcx.sess,
|
||||
pat.span,
|
||||
E0730,
|
||||
"cannot pattern-match on an array without a fixed length",
|
||||
).emit();
|
||||
(inner_ty, tcx.types.err)
|
||||
}
|
||||
}
|
||||
ty::Slice(inner_ty) => (inner_ty, expected_ty),
|
||||
_ => {
|
||||
if !expected_ty.references_error() {
|
||||
let mut err = struct_span_err!(
|
||||
tcx.sess, pat.span, E0529,
|
||||
"expected an array or slice, found `{}`",
|
||||
expected_ty);
|
||||
if let ty::Ref(_, ty, _) = expected_ty.sty {
|
||||
match ty.sty {
|
||||
ty::Array(..) | ty::Slice(..) => {
|
||||
err.help("the semantics of slice patterns changed \
|
||||
recently; see issue #62254");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
err.span_label( pat.span,
|
||||
format!("pattern cannot match with input type `{}`", expected_ty)
|
||||
).emit();
|
||||
}
|
||||
(tcx.types.err, tcx.types.err)
|
||||
}
|
||||
};
|
||||
|
||||
for elt in before {
|
||||
self.check_pat_walk(&elt, inner_ty, def_bm, discrim_span);
|
||||
}
|
||||
if let Some(ref slice) = *slice {
|
||||
self.check_pat_walk(&slice, slice_ty, def_bm, discrim_span);
|
||||
}
|
||||
for elt in after {
|
||||
self.check_pat_walk(&elt, inner_ty, def_bm, discrim_span);
|
||||
}
|
||||
expected_ty
|
||||
}
|
||||
};
|
||||
|
||||
self.write_ty(pat.hir_id, ty);
|
||||
|
||||
// (*) In most of the cases above (literals and constants being
|
||||
// the exception), we relate types using strict equality, even
|
||||
// though subtyping would be sufficient. There are a few reasons
|
||||
// for this, some of which are fairly subtle and which cost me
|
||||
// (nmatsakis) an hour or two debugging to remember, so I thought
|
||||
// I'd write them down this time.
|
||||
//
|
||||
// 1. There is no loss of expressiveness here, though it does
|
||||
// cause some inconvenience. What we are saying is that the type
|
||||
// of `x` becomes *exactly* what is expected. This can cause unnecessary
|
||||
// errors in some cases, such as this one:
|
||||
//
|
||||
// ```
|
||||
// fn foo<'x>(x: &'x int) {
|
||||
// let a = 1;
|
||||
// let mut z = x;
|
||||
// z = &a;
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// The reason we might get an error is that `z` might be
|
||||
// assigned a type like `&'x int`, and then we would have
|
||||
// a problem when we try to assign `&a` to `z`, because
|
||||
// the lifetime of `&a` (i.e., the enclosing block) is
|
||||
// shorter than `'x`.
|
||||
//
|
||||
// HOWEVER, this code works fine. The reason is that the
|
||||
// expected type here is whatever type the user wrote, not
|
||||
// the initializer's type. In this case the user wrote
|
||||
// nothing, so we are going to create a type variable `Z`.
|
||||
// Then we will assign the type of the initializer (`&'x
|
||||
// int`) as a subtype of `Z`: `&'x int <: Z`. And hence we
|
||||
// will instantiate `Z` as a type `&'0 int` where `'0` is
|
||||
// a fresh region variable, with the constraint that `'x :
|
||||
// '0`. So basically we're all set.
|
||||
//
|
||||
// Note that there are two tests to check that this remains true
|
||||
// (`regions-reassign-{match,let}-bound-pointer.rs`).
|
||||
//
|
||||
// 2. Things go horribly wrong if we use subtype. The reason for
|
||||
// THIS is a fairly subtle case involving bound regions. See the
|
||||
// `givens` field in `region_constraints`, as well as the test
|
||||
// `regions-relate-bound-regions-on-closures-to-inference-variables.rs`,
|
||||
// for details. Short version is that we must sometimes detect
|
||||
// relationships between specific region variables and regions
|
||||
// bound in a closure signature, and that detection gets thrown
|
||||
// off when we substitute fresh region variables here to enable
|
||||
// subtyping.
|
||||
}
|
||||
|
||||
fn borrow_pat_suggestion(
|
||||
&self,
|
||||
err: &mut DiagnosticBuilder<'_>,
|
||||
pat: &Pat,
|
||||
inner: &Pat,
|
||||
expected: Ty<'tcx>,
|
||||
) {
|
||||
let tcx = self.tcx;
|
||||
if let PatKind::Binding(..) = inner.node {
|
||||
let binding_parent_id = tcx.hir().get_parent_node(pat.hir_id);
|
||||
let binding_parent = tcx.hir().get(binding_parent_id);
|
||||
debug!("inner {:?} pat {:?} parent {:?}", inner, pat, binding_parent);
|
||||
match binding_parent {
|
||||
hir::Node::Arg(hir::Arg { span, .. }) => {
|
||||
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(inner.span) {
|
||||
err.span_suggestion(
|
||||
*span,
|
||||
&format!("did you mean `{}`", snippet),
|
||||
format!(" &{}", expected),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
hir::Node::Arm(_) |
|
||||
hir::Node::Pat(_) => {
|
||||
// rely on match ergonomics or it might be nested `&&pat`
|
||||
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(inner.span) {
|
||||
err.span_suggestion(
|
||||
pat.span,
|
||||
"you can probably remove the explicit borrow",
|
||||
snippet,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {} // don't provide suggestions in other cases #55175
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_dereferencable(&self, span: Span, expected: Ty<'tcx>, inner: &hir::Pat) -> bool {
|
||||
if let PatKind::Binding(..) = inner.node {
|
||||
if let Some(mt) = self.shallow_resolve(expected).builtin_deref(true) {
|
||||
if let ty::Dynamic(..) = mt.ty.sty {
|
||||
// This is "x = SomeTrait" being reduced from
|
||||
// "let &x = &SomeTrait" or "let box x = Box<SomeTrait>", an error.
|
||||
let type_str = self.ty_to_string(expected);
|
||||
let mut err = struct_span_err!(
|
||||
self.tcx.sess,
|
||||
span,
|
||||
E0033,
|
||||
"type `{}` cannot be dereferenced",
|
||||
type_str
|
||||
);
|
||||
err.span_label(span, format!("type `{}` cannot be dereferenced", type_str));
|
||||
if self.tcx.sess.teach(&err.get_code().unwrap()) {
|
||||
err.note("\
|
||||
This error indicates that a pointer to a trait type cannot be implicitly dereferenced by a \
|
||||
pattern. Every trait defines a type, but because the size of trait implementors isn't fixed, \
|
||||
this type has no compile-time size. Therefore, all accesses to trait types must be through \
|
||||
pointers. If you encounter this error you should try to avoid dereferencing the pointer.
|
||||
|
||||
You can read more about trait objects in the Trait Objects section of the Reference: \
|
||||
https://doc.rust-lang.org/reference/types.html#trait-objects");
|
||||
}
|
||||
err.emit();
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn check_match(
|
||||
&self,
|
||||
expr: &'tcx hir::Expr,
|
||||
|
|
@ -678,8 +59,7 @@ https://doc.rust-lang.org/reference/types.html#trait-objects");
|
|||
let mut all_pats_diverge = Diverges::WarnedAlways;
|
||||
for p in &arm.pats {
|
||||
self.diverges.set(Diverges::Maybe);
|
||||
let binding_mode = ty::BindingMode::BindByValue(hir::Mutability::MutImmutable);
|
||||
self.check_pat_walk(&p, discrim_ty, binding_mode, Some(discrim.span));
|
||||
self.check_pat_top(&p, discrim_ty, Some(discrim.span));
|
||||
all_pats_diverge &= self.diverges.get();
|
||||
}
|
||||
|
||||
|
|
@ -1038,346 +418,4 @@ https://doc.rust-lang.org/reference/types.html#trait-objects");
|
|||
discrim_ty
|
||||
}
|
||||
}
|
||||
|
||||
fn check_pat_struct(
|
||||
&self,
|
||||
pat: &'tcx hir::Pat,
|
||||
qpath: &hir::QPath,
|
||||
fields: &'tcx [hir::FieldPat],
|
||||
etc: bool,
|
||||
expected: Ty<'tcx>,
|
||||
def_bm: ty::BindingMode,
|
||||
discrim_span: Option<Span>,
|
||||
) -> Ty<'tcx> {
|
||||
// Resolve the path and check the definition for errors.
|
||||
let (variant, pat_ty) = if let Some(variant_ty) = self.check_struct_path(qpath, pat.hir_id)
|
||||
{
|
||||
variant_ty
|
||||
} else {
|
||||
for field in fields {
|
||||
self.check_pat_walk(&field.pat, self.tcx.types.err, def_bm, discrim_span);
|
||||
}
|
||||
return self.tcx.types.err;
|
||||
};
|
||||
|
||||
// Type-check the path.
|
||||
self.demand_eqtype_pat(pat.span, expected, pat_ty, discrim_span);
|
||||
|
||||
// Type-check subpatterns.
|
||||
if self.check_struct_pat_fields(pat_ty, pat.hir_id, pat.span, variant, fields, etc, def_bm)
|
||||
{
|
||||
pat_ty
|
||||
} else {
|
||||
self.tcx.types.err
|
||||
}
|
||||
}
|
||||
|
||||
fn check_pat_path(
|
||||
&self,
|
||||
pat: &hir::Pat,
|
||||
path_resolution: (Res, Option<Ty<'tcx>>, &'b [hir::PathSegment]),
|
||||
qpath: &hir::QPath,
|
||||
expected: Ty<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
let tcx = self.tcx;
|
||||
|
||||
// We have already resolved the path.
|
||||
let (res, opt_ty, segments) = path_resolution;
|
||||
match res {
|
||||
Res::Err => {
|
||||
self.set_tainted_by_errors();
|
||||
return tcx.types.err;
|
||||
}
|
||||
Res::Def(DefKind::Method, _) |
|
||||
Res::Def(DefKind::Ctor(_, CtorKind::Fictive), _) |
|
||||
Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) => {
|
||||
report_unexpected_variant_res(tcx, res, pat.span, qpath);
|
||||
return tcx.types.err;
|
||||
}
|
||||
Res::Def(DefKind::Ctor(_, CtorKind::Const), _) | Res::SelfCtor(..) |
|
||||
Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => {} // OK
|
||||
_ => bug!("unexpected pattern resolution: {:?}", res)
|
||||
}
|
||||
|
||||
// Type-check the path.
|
||||
let pat_ty = self.instantiate_value_path(segments, opt_ty, res, pat.span, pat.hir_id).0;
|
||||
self.demand_suptype(pat.span, expected, pat_ty);
|
||||
pat_ty
|
||||
}
|
||||
|
||||
fn check_pat_tuple_struct(
|
||||
&self,
|
||||
pat: &hir::Pat,
|
||||
qpath: &hir::QPath,
|
||||
subpats: &'tcx [P<hir::Pat>],
|
||||
ddpos: Option<usize>,
|
||||
expected: Ty<'tcx>,
|
||||
def_bm: ty::BindingMode,
|
||||
match_arm_pat_span: Option<Span>,
|
||||
) -> Ty<'tcx> {
|
||||
let tcx = self.tcx;
|
||||
let on_error = || {
|
||||
for pat in subpats {
|
||||
self.check_pat_walk(&pat, tcx.types.err, def_bm, match_arm_pat_span);
|
||||
}
|
||||
};
|
||||
let report_unexpected_res = |res: Res| {
|
||||
let msg = format!("expected tuple struct/variant, found {} `{}`",
|
||||
res.descr(),
|
||||
hir::print::to_string(tcx.hir(), |s| s.print_qpath(qpath, false)));
|
||||
let mut err = struct_span_err!(tcx.sess, pat.span, E0164, "{}", msg);
|
||||
match (res, &pat.node) {
|
||||
(Res::Def(DefKind::Fn, _), _) | (Res::Def(DefKind::Method, _), _) => {
|
||||
err.span_label(pat.span, "`fn` calls are not allowed in patterns");
|
||||
err.help("for more information, visit \
|
||||
https://doc.rust-lang.org/book/ch18-00-patterns.html");
|
||||
}
|
||||
_ => {
|
||||
err.span_label(pat.span, "not a tuple variant or struct");
|
||||
}
|
||||
}
|
||||
err.emit();
|
||||
on_error();
|
||||
};
|
||||
|
||||
// Resolve the path and check the definition for errors.
|
||||
let (res, opt_ty, segments) = self.resolve_ty_and_res_ufcs(qpath, pat.hir_id, pat.span);
|
||||
if res == Res::Err {
|
||||
self.set_tainted_by_errors();
|
||||
on_error();
|
||||
return self.tcx.types.err;
|
||||
}
|
||||
|
||||
// Type-check the path.
|
||||
let (pat_ty, res) = self.instantiate_value_path(segments, opt_ty, res, pat.span,
|
||||
pat.hir_id);
|
||||
if !pat_ty.is_fn() {
|
||||
report_unexpected_res(res);
|
||||
return self.tcx.types.err;
|
||||
}
|
||||
|
||||
let variant = match res {
|
||||
Res::Err => {
|
||||
self.set_tainted_by_errors();
|
||||
on_error();
|
||||
return tcx.types.err;
|
||||
}
|
||||
Res::Def(DefKind::AssocConst, _) | Res::Def(DefKind::Method, _) => {
|
||||
report_unexpected_res(res);
|
||||
return tcx.types.err;
|
||||
}
|
||||
Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) => {
|
||||
tcx.expect_variant_res(res)
|
||||
}
|
||||
_ => bug!("unexpected pattern resolution: {:?}", res)
|
||||
};
|
||||
|
||||
// Replace constructor type with constructed type for tuple struct patterns.
|
||||
let pat_ty = pat_ty.fn_sig(tcx).output();
|
||||
let pat_ty = pat_ty.no_bound_vars().expect("expected fn type");
|
||||
|
||||
self.demand_eqtype_pat(pat.span, expected, pat_ty, match_arm_pat_span);
|
||||
|
||||
// Type-check subpatterns.
|
||||
if subpats.len() == variant.fields.len() ||
|
||||
subpats.len() < variant.fields.len() && ddpos.is_some() {
|
||||
let substs = match pat_ty.sty {
|
||||
ty::Adt(_, substs) => substs,
|
||||
_ => bug!("unexpected pattern type {:?}", pat_ty),
|
||||
};
|
||||
for (i, subpat) in subpats.iter().enumerate_and_adjust(variant.fields.len(), ddpos) {
|
||||
let field_ty = self.field_ty(subpat.span, &variant.fields[i], substs);
|
||||
self.check_pat_walk(&subpat, field_ty, def_bm, match_arm_pat_span);
|
||||
|
||||
self.tcx.check_stability(variant.fields[i].did, Some(pat.hir_id), subpat.span);
|
||||
}
|
||||
} else {
|
||||
let subpats_ending = if subpats.len() == 1 { "" } else { "s" };
|
||||
let fields_ending = if variant.fields.len() == 1 { "" } else { "s" };
|
||||
struct_span_err!(tcx.sess, pat.span, E0023,
|
||||
"this pattern has {} field{}, but the corresponding {} has {} field{}",
|
||||
subpats.len(), subpats_ending, res.descr(),
|
||||
variant.fields.len(), fields_ending)
|
||||
.span_label(pat.span, format!("expected {} field{}, found {}",
|
||||
variant.fields.len(), fields_ending, subpats.len()))
|
||||
.emit();
|
||||
on_error();
|
||||
return tcx.types.err;
|
||||
}
|
||||
pat_ty
|
||||
}
|
||||
|
||||
fn check_struct_pat_fields(
|
||||
&self,
|
||||
adt_ty: Ty<'tcx>,
|
||||
pat_id: hir::HirId,
|
||||
span: Span,
|
||||
variant: &'tcx ty::VariantDef,
|
||||
fields: &'tcx [hir::FieldPat],
|
||||
etc: bool,
|
||||
def_bm: ty::BindingMode,
|
||||
) -> bool {
|
||||
let tcx = self.tcx;
|
||||
|
||||
let (substs, adt) = match adt_ty.sty {
|
||||
ty::Adt(adt, substs) => (substs, adt),
|
||||
_ => span_bug!(span, "struct pattern is not an ADT")
|
||||
};
|
||||
let kind_name = adt.variant_descr();
|
||||
|
||||
// Index the struct fields' types.
|
||||
let field_map = variant.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| (field.ident.modern(), (i, field)))
|
||||
.collect::<FxHashMap<_, _>>();
|
||||
|
||||
// Keep track of which fields have already appeared in the pattern.
|
||||
let mut used_fields = FxHashMap::default();
|
||||
let mut no_field_errors = true;
|
||||
|
||||
let mut inexistent_fields = vec![];
|
||||
// Typecheck each field.
|
||||
for field in fields {
|
||||
let span = field.span;
|
||||
let ident = tcx.adjust_ident(field.ident, variant.def_id);
|
||||
let field_ty = match used_fields.entry(ident) {
|
||||
Occupied(occupied) => {
|
||||
struct_span_err!(tcx.sess, span, E0025,
|
||||
"field `{}` bound multiple times \
|
||||
in the pattern",
|
||||
field.ident)
|
||||
.span_label(span,
|
||||
format!("multiple uses of `{}` in pattern", field.ident))
|
||||
.span_label(*occupied.get(), format!("first use of `{}`", field.ident))
|
||||
.emit();
|
||||
no_field_errors = false;
|
||||
tcx.types.err
|
||||
}
|
||||
Vacant(vacant) => {
|
||||
vacant.insert(span);
|
||||
field_map.get(&ident)
|
||||
.map(|(i, f)| {
|
||||
self.write_field_index(field.hir_id, *i);
|
||||
self.tcx.check_stability(f.did, Some(pat_id), span);
|
||||
self.field_ty(span, f, substs)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
inexistent_fields.push(field.ident);
|
||||
no_field_errors = false;
|
||||
tcx.types.err
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
self.check_pat_walk(&field.pat, field_ty, def_bm, None);
|
||||
}
|
||||
let mut unmentioned_fields = variant.fields
|
||||
.iter()
|
||||
.map(|field| field.ident.modern())
|
||||
.filter(|ident| !used_fields.contains_key(&ident))
|
||||
.collect::<Vec<_>>();
|
||||
if inexistent_fields.len() > 0 && !variant.recovered {
|
||||
let (field_names, t, plural) = if inexistent_fields.len() == 1 {
|
||||
(format!("a field named `{}`", inexistent_fields[0]), "this", "")
|
||||
} else {
|
||||
(format!("fields named {}",
|
||||
inexistent_fields.iter()
|
||||
.map(|ident| format!("`{}`", ident))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")), "these", "s")
|
||||
};
|
||||
let spans = inexistent_fields.iter().map(|ident| ident.span).collect::<Vec<_>>();
|
||||
let mut err = struct_span_err!(tcx.sess,
|
||||
spans,
|
||||
E0026,
|
||||
"{} `{}` does not have {}",
|
||||
kind_name,
|
||||
tcx.def_path_str(variant.def_id),
|
||||
field_names);
|
||||
if let Some(ident) = inexistent_fields.last() {
|
||||
err.span_label(ident.span,
|
||||
format!("{} `{}` does not have {} field{}",
|
||||
kind_name,
|
||||
tcx.def_path_str(variant.def_id),
|
||||
t,
|
||||
plural));
|
||||
if plural == "" {
|
||||
let input = unmentioned_fields.iter().map(|field| &field.name);
|
||||
let suggested_name =
|
||||
find_best_match_for_name(input, &ident.as_str(), None);
|
||||
if let Some(suggested_name) = suggested_name {
|
||||
err.span_suggestion(
|
||||
ident.span,
|
||||
"a field with a similar name exists",
|
||||
suggested_name.to_string(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
||||
// we don't want to throw `E0027` in case we have thrown `E0026` for them
|
||||
unmentioned_fields.retain(|&x| x.as_str() != suggested_name.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
if tcx.sess.teach(&err.get_code().unwrap()) {
|
||||
err.note(
|
||||
"This error indicates that a struct pattern attempted to \
|
||||
extract a non-existent field from a struct. Struct fields \
|
||||
are identified by the name used before the colon : so struct \
|
||||
patterns should resemble the declaration of the struct type \
|
||||
being matched.\n\n\
|
||||
If you are using shorthand field patterns but want to refer \
|
||||
to the struct field by a different name, you should rename \
|
||||
it explicitly."
|
||||
);
|
||||
}
|
||||
err.emit();
|
||||
}
|
||||
|
||||
// Require `..` if struct has non_exhaustive attribute.
|
||||
if variant.is_field_list_non_exhaustive() && !adt.did.is_local() && !etc {
|
||||
span_err!(tcx.sess, span, E0638,
|
||||
"`..` required with {} marked as non-exhaustive",
|
||||
kind_name);
|
||||
}
|
||||
|
||||
// Report an error if incorrect number of the fields were specified.
|
||||
if kind_name == "union" {
|
||||
if fields.len() != 1 {
|
||||
tcx.sess.span_err(span, "union patterns should have exactly one field");
|
||||
}
|
||||
if etc {
|
||||
tcx.sess.span_err(span, "`..` cannot be used in union patterns");
|
||||
}
|
||||
} else if !etc {
|
||||
if unmentioned_fields.len() > 0 {
|
||||
let field_names = if unmentioned_fields.len() == 1 {
|
||||
format!("field `{}`", unmentioned_fields[0])
|
||||
} else {
|
||||
format!("fields {}",
|
||||
unmentioned_fields.iter()
|
||||
.map(|name| format!("`{}`", name))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", "))
|
||||
};
|
||||
let mut diag = struct_span_err!(tcx.sess, span, E0027,
|
||||
"pattern does not mention {}",
|
||||
field_names);
|
||||
diag.span_label(span, format!("missing {}", field_names));
|
||||
if variant.ctor_kind == CtorKind::Fn {
|
||||
diag.note("trying to match a tuple variant with a struct variant pattern");
|
||||
}
|
||||
if tcx.sess.teach(&diag.get_code().unwrap()) {
|
||||
diag.note(
|
||||
"This error indicates that a pattern for a struct fails to specify a \
|
||||
sub-pattern for every one of the struct's fields. Ensure that each field \
|
||||
from the struct's definition is mentioned in the pattern, or use `..` to \
|
||||
ignore unwanted fields."
|
||||
);
|
||||
}
|
||||
diag.emit();
|
||||
}
|
||||
}
|
||||
no_field_errors
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ type parameter).
|
|||
mod autoderef;
|
||||
pub mod dropck;
|
||||
pub mod _match;
|
||||
mod pat;
|
||||
pub mod writeback;
|
||||
mod regionck;
|
||||
pub mod coercion;
|
||||
|
|
@ -1103,8 +1104,7 @@ fn check_fn<'a, 'tcx>(
|
|||
// Add formal parameters.
|
||||
for (arg_ty, arg) in fn_sig.inputs().iter().zip(&body.arguments) {
|
||||
// Check the pattern.
|
||||
let binding_mode = ty::BindingMode::BindByValue(hir::Mutability::MutImmutable);
|
||||
fcx.check_pat_walk(&arg.pat, arg_ty, binding_mode, None);
|
||||
fcx.check_pat_top(&arg.pat, arg_ty, None);
|
||||
|
||||
// Check that argument is Sized.
|
||||
// The check for a non-trivial pattern is a hack to avoid duplicate warnings
|
||||
|
|
@ -3630,12 +3630,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
self.check_pat_walk(
|
||||
&local.pat,
|
||||
t,
|
||||
ty::BindingMode::BindByValue(hir::Mutability::MutImmutable),
|
||||
None,
|
||||
);
|
||||
self.check_pat_top(&local.pat, t, None);
|
||||
let pat_ty = self.node_ty(local.pat.hir_id);
|
||||
if pat_ty.references_error() {
|
||||
self.write_ty(local.hir_id, pat_ty);
|
||||
|
|
|
|||
1123
src/librustc_typeck/check/pat.rs
Normal file
1123
src/librustc_typeck/check/pat.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue