Skip to content

Commit b5aca31

Browse files
committed
typeck: refactor default binding mode logic & improve docs
1 parent 17e632d commit b5aca31

File tree

1 file changed

+64
-49
lines changed
  • src/librustc_typeck/check

1 file changed

+64
-49
lines changed

src/librustc_typeck/check/pat.rs

+64-49
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
8989
}
9090
}
9191

92+
const INITIAL_BM: BindingMode = BindingMode::BindByValue(hir::Mutability::Not);
93+
94+
/// Mode for adjusting the expected type and binding mode.
95+
enum AdjustMode {
96+
/// Peel off all immediate reference types.
97+
Peel,
98+
/// Reset binding mode to the inital mode.
99+
Reset,
100+
/// Pass on the input binding mode and expected type.
101+
Pass,
102+
}
103+
92104
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
93105
/// Type check the given top level pattern against the `expected` type.
94106
///
@@ -105,8 +117,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
105117
span: Option<Span>,
106118
origin_expr: bool,
107119
) {
108-
let def_bm = BindingMode::BindByValue(hir::Mutability::Not);
109-
self.check_pat(pat, expected, def_bm, TopInfo { expected, origin_expr, span });
120+
self.check_pat(pat, expected, INITIAL_BM, TopInfo { expected, origin_expr, span });
110121
}
111122

112123
/// Type check the given `pat` against the `expected` type
@@ -123,12 +134,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
123134
) {
124135
debug!("check_pat(pat={:?},expected={:?},def_bm={:?})", pat, expected, def_bm);
125136

126-
let path_resolution = match &pat.kind {
137+
let path_res = match &pat.kind {
127138
PatKind::Path(qpath) => Some(self.resolve_ty_and_res_ufcs(qpath, pat.hir_id, pat.span)),
128139
_ => None,
129140
};
130-
let is_nrp = self.is_non_ref_pat(pat, path_resolution.map(|(res, ..)| res));
131-
let (expected, def_bm) = self.calc_default_binding_mode(pat, expected, def_bm, is_nrp);
141+
let adjust_mode = self.calc_adjust_mode(pat, path_res.map(|(res, ..)| res));
142+
let (expected, def_bm) = self.calc_default_binding_mode(pat, expected, def_bm, adjust_mode);
132143

133144
let ty = match pat.kind {
134145
PatKind::Wild => expected,
@@ -141,7 +152,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
141152
self.check_pat_tuple_struct(pat, qpath, subpats, ddpos, expected, def_bm, ti)
142153
}
143154
PatKind::Path(ref qpath) => {
144-
self.check_pat_path(pat, path_resolution.unwrap(), qpath, expected)
155+
self.check_pat_path(pat, path_res.unwrap(), qpath, expected)
145156
}
146157
PatKind::Struct(ref qpath, fields, etc) => {
147158
self.check_pat_struct(pat, qpath, fields, etc, expected, def_bm, ti)
@@ -223,64 +234,68 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
223234
pat: &'tcx Pat<'tcx>,
224235
expected: Ty<'tcx>,
225236
def_bm: BindingMode,
226-
is_non_ref_pat: bool,
237+
adjust_mode: AdjustMode,
227238
) -> (Ty<'tcx>, BindingMode) {
228-
if is_non_ref_pat {
229-
debug!("pattern is non reference pattern");
230-
self.peel_off_references(pat, expected, def_bm)
231-
} else {
232-
// When you encounter a `&pat` pattern, reset to "by
233-
// value". This is so that `x` and `y` here are by value,
234-
// as they appear to be:
235-
//
236-
// ```
237-
// match &(&22, &44) {
238-
// (&x, &y) => ...
239-
// }
240-
// ```
241-
//
242-
// See issue #46688.
243-
let def_bm = match pat.kind {
244-
PatKind::Ref(..) => ty::BindByValue(hir::Mutability::Not),
245-
_ => def_bm,
246-
};
247-
(expected, def_bm)
239+
match adjust_mode {
240+
AdjustMode::Pass => (expected, def_bm),
241+
AdjustMode::Reset => (expected, INITIAL_BM),
242+
AdjustMode::Peel => self.peel_off_references(pat, expected, def_bm),
248243
}
249244
}
250245

251-
/// Is the pattern a "non reference pattern"?
246+
/// How should the binding mode and expected type be adjusted?
247+
///
252248
/// When the pattern is a path pattern, `opt_path_res` must be `Some(res)`.
253-
fn is_non_ref_pat(&self, pat: &'tcx Pat<'tcx>, opt_path_res: Option<Res>) -> bool {
254-
match pat.kind {
249+
fn calc_adjust_mode(&self, pat: &'tcx Pat<'tcx>, opt_path_res: Option<Res>) -> AdjustMode {
250+
match &pat.kind {
251+
// Type checking these product-like types successfully always require
252+
// that the expected type be of those types and not reference types.
255253
PatKind::Struct(..)
256254
| PatKind::TupleStruct(..)
257255
| PatKind::Tuple(..)
258256
| PatKind::Box(_)
259257
| PatKind::Range(..)
260-
| PatKind::Slice(..) => true,
261-
PatKind::Lit(ref lt) => {
262-
let ty = self.check_expr(lt);
263-
match ty.kind {
264-
ty::Ref(..) => false,
265-
_ => true,
266-
}
267-
}
258+
| PatKind::Slice(..) => AdjustMode::Peel,
259+
// String and byte-string literals result in types `&str` and `&[u8]` respectively.
260+
// All other literals result in non-reference types.
261+
// As a result, we allow `if let 0 = &&0 {}` but not `if let "foo" = &&"foo {}`.
262+
PatKind::Lit(lt) => match self.check_expr(lt).kind {
263+
ty::Ref(..) => AdjustMode::Pass,
264+
_ => AdjustMode::Peel,
265+
},
268266
PatKind::Path(_) => match opt_path_res.unwrap() {
269-
Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => false,
270-
_ => true,
267+
// These constants can be of a reference type, e.g. `const X: &u8 = &0;`.
268+
// Peeling the reference types too early will cause type checking failures.
269+
// Although it would be possible to *also* peel the types of the constants too.
270+
Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => AdjustMode::Pass,
271+
// In the `ValueNS`, we have `SelfCtor(..) | Ctor(_, Const), _)` remaining which
272+
// could successfully compile. The former being `Self` requires a unit struct.
273+
// In either case, and unlike constants, the pattern itself cannot be
274+
// a reference type wherefore peeling doesn't give up any expressivity.
275+
_ => AdjustMode::Peel,
271276
},
272-
// FIXME(or_patterns; Centril | dlrobertson): To keep things compiling
273-
// for or-patterns at the top level, we need to make `p_0 | ... | p_n`
274-
// a "non reference pattern". For example the following currently compiles:
277+
// When encountering a `& mut? pat` pattern, reset to "by value".
278+
// This is so that `x` and `y` here are by value, as they appear to be:
279+
//
275280
// ```
276-
// match &1 {
277-
// e @ &(1...2) | e @ &(3...4) => {}
278-
// _ => {}
281+
// match &(&22, &44) {
282+
// (&x, &y) => ...
279283
// }
280284
// ```
281285
//
282-
// We should consider whether we should do something special in nested or-patterns.
283-
PatKind::Or(_) | PatKind::Wild | PatKind::Binding(..) | PatKind::Ref(..) => false,
286+
// See issue #46688.
287+
PatKind::Ref(..) => AdjustMode::Reset,
288+
// A `_` pattern works with any expected type, so there's no need to do anything.
289+
PatKind::Wild
290+
// Bindings also work with whatever the expected type is,
291+
// and moreover if we peel references off, that will give us the wrong binding type.
292+
// Also, we can have a subpattern `binding @ pat`.
293+
// Each side of the `@` should be treated independently (like with OR-patterns).
294+
| PatKind::Binding(..)
295+
// An OR-pattern just propagates to each individual alternative.
296+
// This is maximally flexible, allowing e.g., `Some(mut x) | &Some(mut x)`.
297+
// In that example, `Some(mut x)` results in `Peel` whereas `&Some(mut x)` in `Reset`.
298+
| PatKind::Or(_) => AdjustMode::Pass,
284299
}
285300
}
286301

@@ -508,7 +523,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
508523
let local_ty = self.local_ty(pat.span, pat.hir_id).decl_ty;
509524
let eq_ty = match bm {
510525
ty::BindByReference(mutbl) => {
511-
// If the binding is like `ref x | ref const x | ref mut x`
526+
// If the binding is like `ref x | ref mut x`,
512527
// then `x` is assigned a value of type `&M T` where M is the
513528
// mutability and T is the expected type.
514529
//

0 commit comments

Comments
 (0)