Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement pattern-binding-modes RFC #44614

Merged
merged 1 commit into from
Oct 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave as is, link to #44848.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

//
// 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