Skip to content

Commit

Permalink
implement pattern-binding-modes RFC
Browse files Browse the repository at this point in the history
  • Loading branch information
tbg committed Oct 6, 2017
1 parent a8feaee commit de55b4f
Show file tree
Hide file tree
Showing 46 changed files with 1,406 additions and 75 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# `match_default_bindings`

The tracking issue for this feature is: [#42640]

[#42640]: https://github.com/rust-lang/rust/issues/42640

------------------------

Match default bindings (also called "default binding modes in match") improves ergonomics for
pattern-matching on references by introducing automatic dereferencing (and a corresponding shift
in binding modes) for large classes of patterns that would otherwise not compile.

For example, under match default bindings,

```rust
#![feature(match_default_bindings)]

fn main() {
let x: &Option<_> = &Some(0);

match x {
Some(y) => {
println!("y={}", *y);
},
None => {},
}
}
```

compiles and is equivalent to either of the below:

```rust
fn main() {
let x: &Option<_> = &Some(0);

match *x {
Some(ref y) => {
println!("y={}", *y);
},
None => {},
}
}
```

or

```rust
fn main() {
let x: &Option<_> = &Some(0);

match x {
&Some(ref y) => {
println!("y={}", *y);
},
&None => {},
}
}
```
14 changes: 9 additions & 5 deletions src/librustc/hir/pat_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,13 @@ impl hir::Pat {
variants
}

/// Checks if the pattern contains any `ref` or `ref mut` bindings,
/// and if yes whether it contains mutable or just immutables ones.
/// Checks if the pattern contains any `ref` or `ref mut` bindings, and if
/// yes whether it contains mutable or just immutables ones.
///
/// FIXME(tschottdorf): this is problematic as the HIR is being scraped,
/// but ref bindings may be implicit after #42640.
/// FIXME(tschottdorf): this is problematic as the HIR is being scraped, but
/// ref bindings are be implicit after #42640 (default match binding modes).
///
/// See #44848.
pub fn contains_explicit_ref_binding(&self) -> Option<hir::Mutability> {
let mut result = None;
self.each_binding(|annotation, _, _, _| {
Expand All @@ -188,7 +190,9 @@ impl hir::Arm {
/// bindings, and if yes whether its containing mutable ones or just immutables ones.
pub fn contains_explicit_ref_binding(&self) -> Option<hir::Mutability> {
// FIXME(tschottdorf): contains_explicit_ref_binding() must be removed
// for #42640.
// for #42640 (default match binding modes).
//
// See #44848.
self.pats.iter()
.filter_map(|pat| pat.contains_explicit_ref_binding())
.max_by_key(|m| match *m {
Expand Down
52 changes: 51 additions & 1 deletion src/librustc/middle/mem_categorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1094,7 +1094,7 @@ impl<'a, 'gcx, 'tcx> MemCategorizationContext<'a, 'gcx, 'tcx> {
}

// FIXME(#19596) This is a workaround, but there should be a better way to do this
fn cat_pattern_<F>(&self, cmt: cmt<'tcx>, pat: &hir::Pat, op: &mut F) -> McResult<()>
fn cat_pattern_<F>(&self, mut cmt: cmt<'tcx>, pat: &hir::Pat, op: &mut F) -> McResult<()>
where F : FnMut(cmt<'tcx>, &hir::Pat)
{
// Here, `cmt` is the categorization for the value being
Expand Down Expand Up @@ -1144,6 +1144,56 @@ impl<'a, 'gcx, 'tcx> MemCategorizationContext<'a, 'gcx, 'tcx> {

debug!("cat_pattern: {:?} cmt={:?}", pat, cmt);

// If (pattern) adjustments are active for this pattern, adjust the `cmt` correspondingly.
// `cmt`s are constructed differently from patterns. For example, in
//
// ```
// match foo {
// &&Some(x, ) => { ... },
// _ => { ... },
// }
// ```
//
// the pattern `&&Some(x,)` is represented as `Ref { Ref { TupleStruct }}`. To build the
// corresponding `cmt` we start with a `cmt` for `foo`, and then, by traversing the
// pattern, try to answer the question: given the address of `foo`, how is `x` reached?
//
// `&&Some(x,)` `cmt_foo`
// `&Some(x,)` `deref { cmt_foo}`
// `Some(x,)` `deref { deref { cmt_foo }}`
// (x,)` `field0 { deref { deref { cmt_foo }}}` <- resulting cmt
//
// The above example has no adjustments. If the code were instead the (after adjustments,
// equivalent) version
//
// ```
// match foo {
// Some(x, ) => { ... },
// _ => { ... },
// }
// ```
//
// Then we see that to get the same result, we must start with `deref { deref { cmt_foo }}`
// instead of `cmt_foo` since the pattern is now `Some(x,)` and not `&&Some(x,)`, even
// though its assigned type is that of `&&Some(x,)`.
for _ in 0..self.tables
.pat_adjustments()
.get(pat.hir_id)
.map(|v| v.len())
.unwrap_or(0) {
cmt = self.cat_deref(pat, cmt, true /* implicit */)?;
}
let cmt = cmt; // lose mutability

// Invoke the callback, but only now, after the `cmt` has adjusted.
//
// To see that this makes sense, consider `match &Some(3) { Some(x) => { ... }}`. In that
// case, the initial `cmt` will be that for `&Some(3)` and the pattern is `Some(x)`. We
// don't want to call `op` with these incompatible values. As written, what happens instead
// is that `op` is called with the adjusted cmt (that for `*&Some(3)`) and the pattern
// `Some(x)` (which matches). Recursing once more, `*&Some(3)` and the pattern `Some(x)`
// result in the cmt `Downcast<Some>(*&Some(3)).0` associated to `x` and invoke `op` with
// that (where the `ref` on `x` is implied).
op(cmt.clone(), pat);

match pat.node {
Expand Down
35 changes: 34 additions & 1 deletion src/librustc/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,24 @@ pub struct TypeckTables<'tcx> {

adjustments: ItemLocalMap<Vec<ty::adjustment::Adjustment<'tcx>>>,

// Stores the actual binding mode for all instances of hir::BindingAnnotation.
/// Stores the actual binding mode for all instances of hir::BindingAnnotation.
pat_binding_modes: ItemLocalMap<BindingMode>,

/// Stores the types which were implicitly dereferenced in pattern binding modes
/// for later usage in HAIR lowering. For example,
///
/// ```
/// match &&Some(5i32) {
/// Some(n) => {},
/// _ => {},
/// }
/// ```
/// leads to a `vec![&&Option<i32>, &Option<i32>]`. Empty vectors are not stored.
///
/// See:
/// https://github.com/rust-lang/rfcs/blob/master/text/2005-match-ergonomics.md#definitions
pat_adjustments: ItemLocalMap<Vec<Ty<'tcx>>>,

/// Borrows
pub upvar_capture_map: ty::UpvarCaptureMap<'tcx>,

Expand Down Expand Up @@ -394,6 +409,7 @@ impl<'tcx> TypeckTables<'tcx> {
node_substs: ItemLocalMap(),
adjustments: ItemLocalMap(),
pat_binding_modes: ItemLocalMap(),
pat_adjustments: ItemLocalMap(),
upvar_capture_map: FxHashMap(),
generator_sigs: ItemLocalMap(),
generator_interiors: ItemLocalMap(),
Expand Down Expand Up @@ -574,6 +590,21 @@ impl<'tcx> TypeckTables<'tcx> {
}
}

pub fn pat_adjustments(&self) -> LocalTableInContext<Vec<Ty<'tcx>>> {
LocalTableInContext {
local_id_root: self.local_id_root,
data: &self.pat_adjustments,
}
}

pub fn pat_adjustments_mut(&mut self)
-> LocalTableInContextMut<Vec<Ty<'tcx>>> {
LocalTableInContextMut {
local_id_root: self.local_id_root,
data: &mut self.pat_adjustments,
}
}

pub fn upvar_capture(&self, upvar_id: ty::UpvarId) -> ty::UpvarCapture<'tcx> {
self.upvar_capture_map[&upvar_id]
}
Expand Down Expand Up @@ -699,6 +730,7 @@ impl<'gcx> HashStable<StableHashingContext<'gcx>> for TypeckTables<'gcx> {
ref node_substs,
ref adjustments,
ref pat_binding_modes,
ref pat_adjustments,
ref upvar_capture_map,
ref closure_tys,
ref closure_kinds,
Expand All @@ -720,6 +752,7 @@ impl<'gcx> HashStable<StableHashingContext<'gcx>> for TypeckTables<'gcx> {
node_substs.hash_stable(hcx, hasher);
adjustments.hash_stable(hcx, hasher);
pat_binding_modes.hash_stable(hcx, hasher);
pat_adjustments.hash_stable(hcx, hasher);
hash_stable_hashmap(hcx, hasher, upvar_capture_map, |up_var_id, hcx| {
let ty::UpvarId {
var_id,
Expand Down
38 changes: 38 additions & 0 deletions src/librustc_const_eval/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,44 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {
}

pub fn lower_pattern(&mut self, pat: &'tcx hir::Pat) -> Pattern<'tcx> {
// When implicit dereferences have been inserted in this pattern, the unadjusted lowered
// pattern has the type that results *after* dereferencing. For example, in this code:
//
// ```
// match &&Some(0i32) {
// Some(n) => { ... },
// _ => { ... },
// }
// ```
//
// the type assigned to `Some(n)` in `unadjusted_pat` would be `Option<i32>` (this is
// determined in rustc_typeck::check::match). The adjustments would be
//
// `vec![&&Option<i32>, &Option<i32>]`.
//
// Applying the adjustments, we want to instead output `&&Some(n)` (as a HAIR pattern). So
// we wrap the unadjusted pattern in `PatternKind::Deref` repeatedly, consuming the
// adjustments in *reverse order* (last-in-first-out, so that the last `Deref` inserted
// gets the least-dereferenced type).
let unadjusted_pat = self.lower_pattern_unadjusted(pat);
self.tables
.pat_adjustments()
.get(pat.hir_id)
.unwrap_or(&vec![])
.iter()
.rev()
.fold(unadjusted_pat, |pat, ref_ty| {
debug!("{:?}: wrapping pattern with type {:?}", pat, ref_ty);
Pattern {
span: pat.span,
ty: ref_ty,
kind: Box::new(PatternKind::Deref { subpattern: pat }),
}
},
)
}

fn lower_pattern_unadjusted(&mut self, pat: &'tcx hir::Pat) -> Pattern<'tcx> {
let mut ty = self.tables.node_id_to_type(pat.hir_id);

let kind = match pat.node {
Expand Down
Loading

0 comments on commit de55b4f

Please sign in to comment.