Auto merge of #46537 - pnkfelix:two-phase-borrows, r=arielb1

[MIR-borrowck] Two phase borrows

This adds limited support for two-phase borrows as described in
  http://smallcultfollowing.com/babysteps/blog/2017/03/01/nested-method-calls-via-two-phase-borrowing/

The support is off by default; you opt into it via the flag `-Z two-phase-borrows`

I have written "*limited* support" above because there are simple variants of the simple `v.push(v.len())` example that one would think should work but currently do not, such as the one documented in the test compile-fail/borrowck/two-phase-reservation-sharing-interference-2.rs

(To be clear, that test is not describing something that is unsound. It is just providing an explicit example of a limitation in the implementation given in this PR. I have ideas on how to fix, but I want to land the work that is in this PR first, so that I can stop repeatedly rebasing this branch.)
This commit is contained in:
bors 2017-12-15 05:40:12 +00:00
commit 84feab34e4
25 changed files with 1183 additions and 256 deletions

View file

@ -0,0 +1,62 @@
// 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.
//
// 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
// except according to those terms.
// revisions: lxl nll
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is an important corner case pointed out by Niko: one is
// allowed to initiate a shared borrow during a reservation, but it
// *must end* before the activation occurs.
//
// FIXME: for clarity, diagnostics for these cases might be better off
// if they specifically said "cannot activate mutable borrow of `x`"
#![allow(dead_code)]
fn read(_: &i32) { }
fn ok() {
let mut x = 3;
let y = &mut x;
{ let z = &x; read(z); }
*y += 1;
}
fn not_ok() {
let mut x = 3;
let y = &mut x;
let z = &x;
*y += 1;
//[lxl]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
//[nll]~^^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
read(z);
}
fn should_be_ok_with_nll() {
let mut x = 3;
let y = &mut x;
let z = &x;
read(z);
*y += 1;
//[lxl]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
// (okay with nll today)
}
fn should_also_eventually_be_ok_with_nll() {
let mut x = 3;
let y = &mut x;
let _z = &x;
*y += 1;
//[lxl]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
//[nll]~^^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
}
fn main() { }

View file

@ -0,0 +1,37 @@
// 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.
//
// 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
// except according to those terms.
// revisions: lxl nll
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is the second counter-example from Niko's blog post
// smallcultfollowing.com/babysteps/blog/2017/03/01/nested-method-calls-via-two-phase-borrowing/
//
// It is "artificial". It is meant to illustrate directly that we
// should allow an aliasing access during reservation, but *not* while
// the mutable borrow is active.
fn main() {
/*0*/ let mut i = 0;
/*1*/ let p = &mut i; // (reservation of `i` starts here)
/*2*/ let j = i; // OK: `i` is only reserved here
/*3*/ *p += 1; // (mutable borrow of `i` starts here, since `p` is used)
/*4*/ let k = i; //[lxl]~ ERROR cannot use `i` because it was mutably borrowed [E0503]
//[nll]~^ ERROR cannot use `i` because it was mutably borrowed [E0503]
/*5*/ *p += 1;
let _ = (j, k, p);
}

View file

@ -0,0 +1,34 @@
// 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.
//
// 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
// except according to those terms.
// revisions: lxl nll
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is the third counter-example from Niko's blog post
// smallcultfollowing.com/babysteps/blog/2017/03/01/nested-method-calls-via-two-phase-borrowing/
//
// It shows that not all nested method calls on `self` are magically
// allowed by this change. In particular, a nested `&mut` borrow is
// still disallowed.
fn main() {
let mut vec = vec![0, 1];
vec.get({
vec.push(2);
//[lxl]~^ ERROR cannot borrow `vec` as mutable because it is also borrowed as immutable
//[nll]~^^ ERROR cannot borrow `vec` as mutable because it is also borrowed as immutable
0
});
}

View file

@ -0,0 +1,37 @@
// 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.
//
// 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
// except according to those terms.
// revisions: lxl nll
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is similar to two-phase-reservation-sharing-interference.rs
// in that it shows a reservation that overlaps with a shared borrow.
//
// Currently, this test fails with lexical lifetimes, but succeeds
// with non-lexical lifetimes. (The reason is because the activation
// of the mutable borrow ends up overlapping with a lexically-scoped
// shared borrow; but a non-lexical shared borrow can end before the
// activation occurs.)
//
// So this test is just making a note of the current behavior.
#![feature(rustc_attrs)]
#[rustc_error]
fn main() { //[nll]~ ERROR compilation successful
let mut v = vec![0, 1, 2];
let shared = &v;
v.push(shared.len());
//[lxl]~^ ERROR cannot borrow `v` as mutable because it is also borrowed as immutable [E0502]
assert_eq!(v, [0, 1, 2, 3]);
}

View file

@ -0,0 +1,46 @@
// 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.
//
// 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
// except according to those terms.
// revisions: lxl nll
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is a corner case that the current implementation is (probably)
// treating more conservatively than is necessary. But it also does
// not seem like a terribly important use case to cover.
//
// So this test is just making a note of the current behavior, with
// the caveat that in the future, the rules may be loosened, at which
// point this test might be thrown out.
fn main() {
let mut vec = vec![0, 1];
let delay: &mut Vec<_>;
{
let shared = &vec;
// we reserve here, which could (on its own) be compatible
// with the shared borrow. But in the current implementation,
// its an error.
delay = &mut vec;
//[lxl]~^ ERROR cannot borrow `vec` as mutable because it is also borrowed as immutable
//[nll]~^^ ERROR cannot borrow `vec` as mutable because it is also borrowed as immutable
shared[0];
}
// the &mut-borrow only becomes active way down here.
//
// (At least in theory; part of the reason this test fails is that
// the constructed MIR throws in extra &mut reborrows which
// flummoxes our attmpt to delay the activation point here.)
delay.push(2);
}

View file

@ -0,0 +1,30 @@
// 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.
//
// 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
// except according to those terms.
// revisions: lxl nll
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is the first counter-example from Niko's blog post
// smallcultfollowing.com/babysteps/blog/2017/03/01/nested-method-calls-via-two-phase-borrowing/
// of a danger for code to crash if we just turned off the check for whether
// a mutable-borrow aliases another borrow.
fn main() {
let mut v: Vec<String> = vec![format!("Hello, ")];
v[0].push_str({
v.push(format!("foo"));
//[lxl]~^ ERROR cannot borrow `v` as mutable more than once at a time [E0499]
//[nll]~^^ ERROR cannot borrow `v` as mutable more than once at a time [E0499]
"World!"
});
}

View file

@ -0,0 +1,21 @@
// 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.
//
// 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
// except according to those terms.
// revisions: lxl nll
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is the "goto example" for why we want two phase borrows.
fn main() {
let mut v = vec![0, 1, 2];
v.push(v.len());
assert_eq!(v, [0, 1, 2, 3]);
}

View file

@ -0,0 +1,27 @@
// 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.
//
// 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
// except according to those terms.
// revisions: lxl nll
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
fn main() {
let mut a = 0;
let mut b = 0;
let p = if maybe() {
&mut a
} else {
&mut b
};
use_(p);
}
fn maybe() -> bool { false }
fn use_<T>(_: T) { }