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

Conversation

tbg
Copy link
Contributor

@tbg tbg commented Sep 15, 2017

See the RFC and tracking issue #42640

@rust-highfive
Copy link
Collaborator

r? @nikomatsakis

(rust_highfive has picked a reviewer for you, use r? to override)

@tbg
Copy link
Contributor Author

tbg commented Sep 16, 2017

@nikomatsakis I updated the mem categorization code. Maybe I missed something, but your example borrowcks now (see match-defbm-ref-region.rs).

@tbg tbg force-pushed the pat_adjustments branch 2 times, most recently from 30edb33 to 5c972f4 Compare September 16, 2017 14:25
@carols10cents carols10cents added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Sep 18, 2017
@bors
Copy link
Contributor

bors commented Sep 18, 2017

☔ The latest upstream changes (presumably #44678) made this pull request unmergeable. Please resolve the merge conflicts.

@nikomatsakis
Copy link
Contributor

@tschottdorf sorry for being slow. Start of the impl period has gotten me pretty slammed!

@tbg
Copy link
Contributor Author

tbg commented Sep 19, 2017 via email

Copy link
Contributor

@nikomatsakis nikomatsakis left a comment

Choose a reason for hiding this comment

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

I left a few nits and suggestions. It seems like the major unknown is how to deal with the "patterns contain binding" test. I know that @arielb1 and I had some discussion about this at some point that I'd like to re-read. =)

@@ -1144,8 +1144,53 @@ impl<'a, 'gcx, 'tcx> MemCategorizationContext<'a, 'gcx, 'tcx> {

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

// FIXME(tschottdorf): I'm not clear on whether this should also see the adjusted `cmt`.
// I would assume so, but @nikomatsakis pointed me at just after this line to make the
// adjustments.
Copy link
Contributor

Choose a reason for hiding this comment

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

I did point you to do the adjustment after, but I think I was wrong. I think we should move this callback to occur after. At first I thought we would do the callback both before and after each adjustment, but since we supply both the pattern and the cmt, I think it's better to just do a callback after the adjustments. We are therefore giving (a) the pattern that is being used and (b) the cmt that is being matched by this pattern. Looking at the use-sites of this function, all of them expect this and will work properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, will give that a try. Do you have an example or another way of testing that would break the current code and works with the callback invoked at the right place?

Copy link
Contributor

Choose a reason for hiding this comment

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

mm let me think about it. There may be such a thing.

Copy link
Contributor

Choose a reason for hiding this comment

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

I cannot find a way that it makes any difference -- all the consumers of this except for an obscure one in clippy only care about binding patterns, and those are never adjusted. However, it still seems more correct. Right now, we are passing in the bare pattern paired with the cmt that corresponds to (logically) the adjusted pattern. This is also coherent but it seems a bit .. less expected. i.e., if I match on the pattern and get (e.g.) PatTuple, I would expect the cmt to have tuple type (but in this code, it may have reference type).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just want to double check your comment above because the code that you commented on gives the cmt for the bare pattern and the bare pattern, whereas moving the callback gives the adjusted cmt and (still) the bare pattern. Your wording makes it sound like you'd actually prefer option 1, which is the current code (right?).

Copy link
Contributor

Choose a reason for hiding this comment

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

So, what I meant is this:

  • Consider first match &Some(3) { &Some(ref x) => .
    • When cat_pattern is first invoked, we have the cmt C for the discriminant (&Some(3)) and the pattern &Some(ref x), and we invoke the callback with that
    • We then process the pattern, yielding the CMT for *&Some(3) and the pattern Some(ref x), and we invoke the callback with that.
    • Finally, we process the CMT one step further to Downcast<Some>(*&Some(3)).0 and invoke callback with with the pattern ref x.
  • Under the code as it is there now:
    • When cat_pattern is first invoked, we still have the same cmt C for the discriminant (&Some(3)), but the pattern is for Some(x). We invoke the callback with that.
    • Then we adjust the CMT to *&Some(3). No callback occurs after this.
    • Then we process the pattern, downcasting to Downcast<Some>(*&Some(3)).0, and invoke callback with pattern x, where the ref is implied.

These don't quite match -- there aren't the same number of callbacks, to start, but also the cmt and patterns don't match up. If we move the callback to after the adjustment, we will get:

  • Under the code as proposed:
    • When cat_pattern is first invoked, we still have the same cmt C for the discriminant (&Some(3)), but the pattern is for Some(x).
    • Then we adjust the CMT to *&Some(3).
    • Now we callback with CMT *&Some(3) and pattern Some(x), just as before.
    • Then we process the pattern, downcasting to Downcast<Some>(*&Some(3)).0, and invoke callback with pattern x, where the ref is implied.

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.

)
}

pub fn lower_pattern_unadjusted(&mut self, pat: &'tcx hir::Pat) -> Pattern<'tcx> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Make this private.

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.

}

/// The `is_arg` argument indicates whether this pattern is the
/// *outermost* pattern in an argument (e.g., in `fn foo(&x:
/// &u32)`, it is true for the `&x` pattern but not `x`). This is
/// used to tailor error reporting.
pub fn check_pat_arg(&self, pat: &'gcx hir::Pat, expected: Ty<'tcx>, is_arg: bool) {
pub fn check_pat_arg(&self, pat: &'gcx hir::Pat, mut expected: Ty<'tcx>,
mut def_bm: ty::BindingMode, is_arg: bool) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I'd prefer the arguments to each be on their own line.

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.

PatKind::TupleStruct(..) |
PatKind::Tuple(..) |
PatKind::Box(_) |
// PatKind::Lit(_) | // FIXME(tschottdorf): causes lots of errors
Copy link
Contributor

Choose a reason for hiding this comment

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

OK, we gotta figure this out.

Copy link
Contributor

Choose a reason for hiding this comment

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

I forget, did you have examples?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Among possibly other things, we treat string literals incorrectly.

match s {
            "inf" => T::INFINITY,
            "NaN" => T::NAN,
            _ => { return Err(pfe_invalid()); }
        }
   Compiling core v0.0.0 (file:///Users/tschottdorf/rust/binding-modes/src/libcore)
error[E0308]: mismatched types
   --> src/libcore/num/dec2flt/mod.rs:219:13
    |
219 |             "inf" => T::INFINITY,
    |             ^^^^^ expected str, found reference
    |
    = note: expected type `str`
               found type `&'static str`

error[E0308]: mismatched types
   --> src/libcore/num/dec2flt/mod.rs:220:13
    |
220 |             "NaN" => T::NAN,
    |             ^^^^^ expected str, found reference
    |
    = note: expected type `str`
               found type `&'static str`

error[E0308]: mismatched types
   --> src/libcore/str/mod.rs:130:13
    |
130 |             "true"  => Ok(true),
    |             ^^^^^^ expected str, found reference
    |
    = note: expected type `str`
               found type `&'static str`

error[E0308]: mismatched types
   --> src/libcore/str/mod.rs:131:13
    |
131 |             "false" => Ok(false),
    |             ^^^^^^^ expected str, found reference
    |
    = note: expected type `str`
               found type `&'static str`

error: aborting due to 4 previous errors

error: Could not compile `core`.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah it seems like string and byte literals (and maybe slices?) are the other places this would go wrong. But I guess more generally one can have a constant with references in it (e.g., const X: &'static u32 = &22 -- I forget if you can use such a constant in a match though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a computation of the type and make the true/false depend on whether there's a TyRef. May not be the right thing to do, but at least it works somewhat.

PatKind::Slice(..) => true,
PatKind::Path(ref qpath) => {
// FIXME(tschottdorf): is it OK to call this here?
let (def, _, _) = self.resolve_ty_and_def_ufcs(qpath, pat.id, pat.span);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm. It's not great to call this here, in that it can report an error, which might then get reported again later on (by check_pat_path). Not sure how best to fix this though!

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, we might be able to just modify resolve_ty_and_def_ufcs to cache the result. It already writes the final result into a table:

// Write back the new resolution.
let hir_id = self.tcx.hir.node_to_hir_id(node_id);
self.tables.borrow_mut().type_dependent_defs_mut().insert(hir_id, def);

seems like we can check that cache in the case that we get TypeRelative.

(cc @eddyb -- this seem ok to you?)

@@ -0,0 +1,38 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
Copy link
Contributor

Choose a reason for hiding this comment

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

So I've been advocating for us to organize the tests for new language features like this by putting them into a directory. So can we move this to ths directory:

src/test/compile-fail/rfc-XXX-default-binding-mode/

The idea is that we should be able to browse this directory (as well as corresponding directories in src/test/run-pass and src/test/ui) to get a good idea of how the feature behaves.

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.

@@ -0,0 +1,49 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
Copy link
Contributor

Choose a reason for hiding this comment

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

move this file to a directory, as described before

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.

//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
Copy link
Contributor

Choose a reason for hiding this comment

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

directory

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.

// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
Copy link
Contributor

Choose a reason for hiding this comment

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

directory

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.

// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
Copy link
Contributor

Choose a reason for hiding this comment

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

directory

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.

@nikomatsakis
Copy link
Contributor

@tschottdorf

I think that it might be considered unfortunate that
typeck is now unaware of the fact that autoderefs were inserted, and so the error messages may
refer to types that weren't written down.

Was there some particular test that caught your eye? Most of them actually looked like improvements to me.

Copy link
Contributor Author

@tbg tbg left a comment

Choose a reason for hiding this comment

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

Thanks for the review @nikomatsakis! Haven't changed anything (will do tomorrow), just posted a few responses to comments to get you unstuck.

@@ -1144,8 +1144,53 @@ impl<'a, 'gcx, 'tcx> MemCategorizationContext<'a, 'gcx, 'tcx> {

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

// FIXME(tschottdorf): I'm not clear on whether this should also see the adjusted `cmt`.
// I would assume so, but @nikomatsakis pointed me at just after this line to make the
// adjustments.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, will give that a try. Do you have an example or another way of testing that would break the current code and works with the callback invoked at the right place?

// FIXME(tschottdorf): don't call contains_explicit_ref_binding, which
// is problematic as the HIR is being scraped, but ref bindings may be
// implicit after #42640. We need to make sure that pat_adjustments
// (once introduced) is populated by the time we get here.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

PatKind::TupleStruct(..) |
PatKind::Tuple(..) |
PatKind::Box(_) |
// PatKind::Lit(_) | // FIXME(tschottdorf): causes lots of errors
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Among possibly other things, we treat string literals incorrectly.

match s {
            "inf" => T::INFINITY,
            "NaN" => T::NAN,
            _ => { return Err(pfe_invalid()); }
        }
   Compiling core v0.0.0 (file:///Users/tschottdorf/rust/binding-modes/src/libcore)
error[E0308]: mismatched types
   --> src/libcore/num/dec2flt/mod.rs:219:13
    |
219 |             "inf" => T::INFINITY,
    |             ^^^^^ expected str, found reference
    |
    = note: expected type `str`
               found type `&'static str`

error[E0308]: mismatched types
   --> src/libcore/num/dec2flt/mod.rs:220:13
    |
220 |             "NaN" => T::NAN,
    |             ^^^^^ expected str, found reference
    |
    = note: expected type `str`
               found type `&'static str`

error[E0308]: mismatched types
   --> src/libcore/str/mod.rs:130:13
    |
130 |             "true"  => Ok(true),
    |             ^^^^^^ expected str, found reference
    |
    = note: expected type `str`
               found type `&'static str`

error[E0308]: mismatched types
   --> src/libcore/str/mod.rs:131:13
    |
131 |             "false" => Ok(false),
    |             ^^^^^^^ expected str, found reference
    |
    = note: expected type `str`
               found type `&'static str`

error: aborting due to 4 previous errors

error: Could not compile `core`.

@nikomatsakis
Copy link
Contributor

OK, so, I was thinking some more about the problem of how to replace the code that scans (syntactically) for ref bindings. I think that we can keep using the syntactical check and things will be ok, but I'm not sure I want to. Let me lay out the reasoning in full. @arielb1 I'd appreciate your thoughts on this! This is rather complicated.

Let's focus on the case of a let binding with an explicit type, as it is the most complicated:

let P: T = E

What makes this complicated is that we sometimes need to coerce the result of evaluating E to the type T (i.e., subtyping won't do, because runtime adjustments are needed). But that combines oddly with ref bindings, since coercion will produce a new temporary value, and we are then "borrowing" that new value. But when you write (for example) something with an lvalue initializer:

let ref mut x = something

this is generally equivalent to let x = &mut something. That is, we are borrowing something.

So what we do TODAY is to say this, effectively:

  • If there are (syntactic) ref-bindings in the pattern P, then E is treated "as an lvalue". That is, we will not permit coercions, the patterns will match against E "in place". If E is a local variable, field, etc, then you will get references into that storage.
  • If there are NOT (syntactic) ref-bindings in the pattern P, then E is treated "as an rvalue". That is, we permit coercions, and we decompose the result. This break-down works fine today because not having syntactic ref-bindings means there are no ref-bindings at all: so it's fine that we coerce into a fresh value, as we are taking ownership of various bits of the coerced result, not borrowing them.

However, in the new system, we now have to account for the possibility that there will be "type-based" ref-bindings in the pattern P that are not syntactically evident. However, we know that if such ref-bindings exist, they occur "beneath" a & or &mut reference. That is, there may be borrows we cannot see, but they will only be borrows of the referents of a reference, not the reference itself.

I think this means we don't have to worry about the big danger that we were afraid of. In particular, no coercion ever modifies the referent of a reference -- we don't know where that memory lives, and we don't know that we have the freedom to change it. In other words, if we coerce the result of E, it will only affect the owned content of that result, not its borrowed content -- and for their to be borrows of the owned content, they must be done with a ref binding.

(There is one exception of sorts: we do permit &'a mut (Trait + 'b) to be coerced to &'a mut (Trait + 'c) where 'b: 'c. This would ordinarily be unsound, but it's ok because we don't permit reassignment of unsized types. It's not clear that such a coercion matters here anyway, since there is no way to write a pattern that matches the "trait" part without the &mut. But even if it were possible, the resulting binding would not permit mutation of its referent.)

The other cases where we use this check, I think, can never perform coercion, and so are not as problematic. They only have to worry about subtyping, and in that case the same logic above applies -- we will only ever take a ref mut binding to the referent an &mut, which is invariant anyhow:

  • match -- no coercion is done
  • let P = E -- the "expected type" is a fresh inference variable, so no coercion performed

@arielb1
Copy link
Contributor

arielb1 commented Sep 25, 2017

@nikomatsakis

Now that I'm reading this again:
In HAIR/MIR, the soundness of the situation is quite simple: coercions and subtyping occur only on vexprs. Subtyping can operate on references within a vexpr, but it's still at heart a vexpr operation. Match expressions do their subtyping when they bind the (potentially references) to the fields of the scrutinee into the bindings.

If we just wanted to be consistent with that, then the #23116 example:

#![allow(dead_code)]
use std::fmt::Debug;
struct S(Box<Debug + 'static>);
impl S {
    fn bar<'a>(&'a mut self)->&'a mut Box<Debug + 'a> {
        match self.0 { ref mut x => x } // should not compile, but does
    }
}
fn main() {}

Would perform a lexpr->vexpr->lexpr conversion, and therefore a temporary, creating MIR as follows:

temp = self.0; // lexpr -> vexpr
scrutinee_temp = &mut temp; // vexpr -> lexpr + coercion
x = &mut (*scrutinee_temp); // match binding
ret_ptr = x; // return value assignment
return

Which would of course cause a "borrow does not live long enough" when it catches you trying to return a borrow of the local temp.

Obviously, creating such coercion temporaries by default will annoy anyone who tries to use ref patterns to actually match parts of the scrutinee value by reference, so we don't. If we see a ref pattern, we prevent coercions and the resulting round-trips.

However, if all the ref bindings occur behind a reference, creating the temporary can't actually be annoying in this way - you don't take references to the value, so the temporary is invisible and we don't need to avoid it. Therefore, avoiding coercions exactly when there are explicit ref patterns is a implementable and non-annoying strategy.

An aside

The above description is not actually implementation-coherent with typeck - to satisfy closure inference, match bindings can't use subtyping, so rustc actually sometimes performs subtyping on immutable lexprs - see this comment:

// (*) In most of the cases above (literals and constants being
// the exception), we relate types using strict equality, evewn
// 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:
// it will cause errors in a case like this:
//
// ```
// 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_inference`, 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.

Note that typeck's strategy is imperfect and leads to spurious errors in some situations

fn foo<'x>(mut x: (&'x isize, ())) {
    let a = 1;
    let (mut _z, ref _y) = x;
    _z = &a; //~ ERROR no subtyping for you!
}
      
fn main() {}

We'll also have to solve the closure inference wonkyness when we get to MIR-based regionck, but hopefully the MIR-based regionck won't have wonky anyway.

@arielb1
Copy link
Contributor

arielb1 commented Sep 25, 2017

While this argument works today, it will put us in a somewhat sticky situation when we get DerefPure:

struct Newtype<T>(T);

impl<T> Deref for Newtype<T> {
    type Target = T;
    fn deref(&self) -> &T { &self.0 }
}
impl<T> DerefMut for Newtype<T> {
    fn deref_mut(&mut self) -> &mut T { &mut self.0 }
}
unsafe impl<T> DerefPure for Newtype<T> { /* ... */ }

fn main() {
    let n = Newtype(("hello",));
    {
        let s = "hi there".to_string();
        let (mut d,) = n;
        *d = &s; // create a temporary? fail? both options are unsatisfying
    }
}

@nikomatsakis
Copy link
Contributor

My take is that we can land this without fully resolving the "coercion chicken" question. I've filed #44848 to track that.

@bors
Copy link
Contributor

bors commented Sep 26, 2017

☔ The latest upstream changes (presumably #44735) made this pull request unmergeable. Please resolve the merge conflicts.

match &&&&i {
1 ... 3 => panic!(),
3 ... 8 => {},
_ => {},
Copy link
Contributor

@KamilaBorowska KamilaBorowska Sep 26, 2017

Choose a reason for hiding this comment

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

Shouldn't default variant panic too, considering it's unexpected result value?

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.

@nikomatsakis
Copy link
Contributor

@tschottdorf I wrote up a list of test cases here. Can you just check that you have tests covering those cases?

@tbg
Copy link
Contributor Author

tbg commented Sep 28, 2017

@nikomatsakis confirmed, though one of your test cases doesn't compile (out of scope of this PR, see #42640 (comment)). I added a compile-fail test that documents this but you probably want to file an issue unless there's already one.

Could you review the FIXMEs (just search for FIXME(tschottdorf) in the full diff and leave a comment on each, the comment could just be "leave as is for now"). I do want to address them, though if you're desperate about merging this, I could do it in a follow-up.

/// 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 may be implicit after #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.

@@ -188,7 +188,7 @@ 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.

@@ -1144,8 +1144,53 @@ impl<'a, 'gcx, 'tcx> MemCategorizationContext<'a, 'gcx, 'tcx> {

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

// FIXME(tschottdorf): I'm not clear on whether this should also see the adjusted `cmt`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Change as discussed below (move callback later).

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.

.get(pat.hir_id)
.map(|v| v.len())
.unwrap_or(0) {
// FIXME(tschottdorf): is implicit==true correct?
Copy link
Contributor

Choose a reason for hiding this comment

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

It is correct. If you make it false, I think it would affect how the error in this example is printed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack, done.

// Ditto with byte strings.
fn with_bytes() {
let s: &'static [u8] = b"abc";
let result = match s {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should test match &s { too.

Copy link
Contributor

Choose a reason for hiding this comment

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

(Though I think this would maybe give an error?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, this gives an error. What would you like me to do? Move to ui tests & file an issue? Try to fix?

    let s: &'static str = "abc";

    match &s {
            "abc" => true,
            _ => panic!(),
    };

// option. This file may not be copied, modified, or distributed
// except according to those terms.

// FIXME(tschottdorf): what other noteworthy literals are there?
Copy link
Contributor

Choose a reason for hiding this comment

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

Booleans? Not much else really.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have a test with named constants of struct type etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Like this?

#[derive(PartialEq, Eq)]
struct Foo {
    bar: i32,    
}

const FOO: Foo = Foo{bar: 5};

fn main() {
    let f = Foo{bar:6};
    
    match &f {
        FOO => {},
        _ => panic!(),
    }
}

Doesn't compile (but should), added to ui tests for now.

@nikomatsakis
Copy link
Contributor

@tschottdorf

Could you review the FIXMEs

Done

Copy link
Contributor Author

@tbg tbg left a comment

Choose a reason for hiding this comment

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

Thanks! Mostly done except for avoiding double type errors. Plus there may be some non-compiling tests that you'll want to advise me on/open follow-up issues for (all have comments in this review).

/// 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 may be implicit after #42640 (default match binding modes).
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.

@@ -188,7 +188,7 @@ 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 Author

Choose a reason for hiding this comment

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

Done.

@@ -1144,8 +1144,53 @@ impl<'a, 'gcx, 'tcx> MemCategorizationContext<'a, 'gcx, 'tcx> {

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

// FIXME(tschottdorf): I'm not clear on whether this should also see the adjusted `cmt`.
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.

@@ -1144,8 +1144,53 @@ impl<'a, 'gcx, 'tcx> MemCategorizationContext<'a, 'gcx, 'tcx> {

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

// FIXME(tschottdorf): I'm not clear on whether this should also see the adjusted `cmt`.
// I would assume so, but @nikomatsakis pointed me at just after this line to make the
// adjustments.
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.

.get(pat.hir_id)
.map(|v| v.len())
.unwrap_or(0) {
// FIXME(tschottdorf): is implicit==true correct?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack, done.

let _: &i32 = match x {
// Here, each of the patterns are treated independently
//
// FIXME(tschottdorf): make this compile without the actual `|`.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Filed #44912.

// option. This file may not be copied, modified, or distributed
// except according to those terms.

// FIXME(tschottdorf): what other noteworthy literals are there?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Like this?

#[derive(PartialEq, Eq)]
struct Foo {
    bar: i32,    
}

const FOO: Foo = Foo{bar: 5};

fn main() {
    let f = Foo{bar:6};
    
    match &f {
        FOO => {},
        _ => panic!(),
    }
}

Doesn't compile (but should), added to ui tests for now.

// Ditto with byte strings.
fn with_bytes() {
let s: &'static [u8] = b"abc";
let result = match s {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, this gives an error. What would you like me to do? Move to ui tests & file an issue? Try to fix?

    let s: &'static str = "abc";

    match &s {
            "abc" => true,
            _ => panic!(),
    };

}
}
PatKind::Path(ref qpath) => {
// FIXME(tschottdorf): is it OK to call this here?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. I'll try to do that tomorrow.

PatKind::Range(..) |
PatKind::Slice(..) => true,
PatKind::Lit(ref lt) => {
// FIXME(tschottdorf): is it OK to call this here?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll take a look at this tomorrow.

@tbg tbg force-pushed the pat_adjustments branch 2 times, most recently from 6e5d235 to 185a738 Compare September 29, 2017 19:07
@tbg
Copy link
Contributor Author

tbg commented Sep 29, 2017

Added a feature gate and docs in the unstable book. Mod bikeshedding on the feature gate name and avoiding doubly reporting errors I think I'm done here. Anything else missing?

@tbg tbg force-pushed the pat_adjustments branch 2 times, most recently from af06651 to bc651af Compare September 29, 2017 21:17
@tbg
Copy link
Contributor Author

tbg commented Sep 29, 2017

@nikomatsakis, re #44614 (comment):

what's an example that would return an error twice? I'll need that to test that I fixed something, and wasn't able to put one together myself.

I did add caching in resolve_ty_and_def_ufcs, see if that's what you had in mind.

@tbg
Copy link
Contributor Author

tbg commented Sep 29, 2017

Re: #44614 (comment) (same problem for Lit), assume I wanted to pre-typecheck all the Lit instances, how would I do that? Add a separate walk that populates a map, and then pass that map into check_pat_arg and look into the map for each Lit? Would also like an example that fires a double error, wasn't able to put one together.

@nikomatsakis
Copy link
Contributor

Regarding the double errors: This is an example of a test-case that gives double errors without your fix, but no longer does.

fn main() {
    let foo = 22;
    match foo {
        u32::XXX => { }
        _ => { }
    }
}

@nikomatsakis
Copy link
Contributor

@tschottdorf

Add a separate walk that populates a map, and then pass that map into check_pat_arg and look into the map for each Lit?

I imagine something like this, yes. Basically a pre-pass over all the patterns that finds literals and type-checks them -- this would probably happen when check_pat is first invoked. Then during the rest of the code that walks patterns, when we encounter a pattern literal, we would not invoke

 let ty = self.check_expr(&lt);

but rather

let ty = self.node_ty(lt.hir_id);

which will read out the (previously computed) type .

In other words, you don't need to add a new map, we already have the tables.

In terms of a test that would go awry... hmm... that may be a bit harder. =) I am trying to think if it's possible to get an error while type-checking a literal! Maybe it's not.

@tbg
Copy link
Contributor Author

tbg commented Oct 6, 2017

Squashed and ready for more review and merge.

@nikomatsakis double-error worked fine. As for the literals, I realized I didn't have to add an extra walk -- we were always calling check_expr twice in the same order, so now I just use node_ty for the second callsite.

@nikomatsakis
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Oct 6, 2017

📌 Commit de55b4f has been approved by nikomatsakis

@bors
Copy link
Contributor

bors commented Oct 7, 2017

⌛ Testing commit de55b4f with merge e11f6d5...

bors added a commit that referenced this pull request Oct 7, 2017
@bors
Copy link
Contributor

bors commented Oct 7, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: nikomatsakis
Pushing e11f6d5 to master...

--> $DIR/explicit-mut.rs:19:13
|
18 | Some(n) => {
| - consider changing this to `n`
Copy link
Member

Choose a reason for hiding this comment

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

this kind of lint seems a bit weird since n is already n.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants