Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 3 additions & 2 deletions src/librustc_typeck/check/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,8 @@ impl<'a, 'gcx, 'tcx> CastCheck<'tcx> {
let f = self.expr_ty.fn_sig(fcx.tcx);
let res = fcx.try_coerce(self.expr,
self.expr_ty,
fcx.tcx.mk_fn_ptr(f));
fcx.tcx.mk_fn_ptr(f),
false);
if !res.is_ok() {
return Err(CastError::NonScalar);
}
Expand Down Expand Up @@ -616,7 +617,7 @@ impl<'a, 'gcx, 'tcx> CastCheck<'tcx> {
}

fn try_coercion_cast(&self, fcx: &FnCtxt<'a, 'gcx, 'tcx>) -> bool {
fcx.try_coerce(self.expr, self.expr_ty, self.cast_ty).is_ok()
fcx.try_coerce(self.expr, self.expr_ty, self.cast_ty, false).is_ok()
}
}

Expand Down
37 changes: 27 additions & 10 deletions src/librustc_typeck/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ struct Coerce<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
fcx: &'a FnCtxt<'a, 'gcx, 'tcx>,
cause: ObligationCause<'tcx>,
use_lub: bool,
/// Determines whether or not allow_two_phase_borrow is set on any
/// autoref adjustments we create while coercing. We don't want to
/// allow deref coercions to create two-phase borrows, at least initially,
/// but we do need two-phase borrows for function argument reborrows.
/// See #47489 and #48598
allow_two_phase: bool,
}

impl<'a, 'gcx, 'tcx> Deref for Coerce<'a, 'gcx, 'tcx> {
Expand Down Expand Up @@ -123,10 +129,13 @@ fn success<'tcx>(adj: Vec<Adjustment<'tcx>>,
}

impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {
fn new(fcx: &'f FnCtxt<'f, 'gcx, 'tcx>, cause: ObligationCause<'tcx>) -> Self {
fn new(fcx: &'f FnCtxt<'f, 'gcx, 'tcx>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: these days, I tend to move over to rustfmt style when changing a sig anyway

fn new(
    fcx: ...
    cause: ...
    allow_two_phase: ...,
) -> Self {

cause: ObligationCause<'tcx>,
allow_two_phase: bool) -> Self {
Coerce {
fcx,
cause,
allow_two_phase,
use_lub: false,
}
}
Expand Down Expand Up @@ -424,10 +433,7 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {
let mutbl = match mt_b.mutbl {
hir::MutImmutable => AutoBorrowMutability::Immutable,
hir::MutMutable => AutoBorrowMutability::Mutable {
// Deref-coercion is a case where we deliberately
// disallow two-phase borrows in its initial
// deployment; see discussion on PR #47489.
allow_two_phase_borrow: false,
allow_two_phase_borrow: self.allow_two_phase,
}
};
adjustments.push(Adjustment {
Expand Down Expand Up @@ -473,6 +479,9 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {
let mutbl = match mt_b.mutbl {
hir::MutImmutable => AutoBorrowMutability::Immutable,
hir::MutMutable => AutoBorrowMutability::Mutable {
// We don't allow two-phase borrows here, at least for initial
// implementation. If it happens that this coercion is a function argument,
// the reborrow in coerce_borrowed_ptr will pick it up.
allow_two_phase_borrow: false,
}
};
Expand Down Expand Up @@ -751,13 +760,14 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
pub fn try_coerce(&self,
expr: &hir::Expr,
expr_ty: Ty<'tcx>,
target: Ty<'tcx>)
target: Ty<'tcx>,
allow_two_phase: 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: maybe we can make a wrapper? I don't like the way "random bool parameters" read in the surrounding code. Maybe something like:

fn try_coerce(...) {
    self.try_coerce_full(..., false)
}

fn try_coerce_with_two_phase_borrow(...) {
    self.try_coerce_full(..., true)
}

fn try_coerce_full(...) { // as today
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively, since I see that the bool parameter propagates elsewhere, can we make it a newtype'd bool?

struct TwoPhase(bool);

so that callers look like:

self.try_coerce(..., TwoPhase(false))

This also gives us a central place to write docs that talk about 2-phase borrowing...

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 also wasn't super enthused about leaking that parameter everywhere. I think I'll take the newtype'd bool approach here and try to do a bit of a writeup. I think it makes sense to push that newtype through all the way until it's consumed in MIR borrwck which is going to touch a bit more code. I'll put that in a separate commit in case we want to back it out later.

-> RelateResult<'tcx, Ty<'tcx>> {
let source = self.resolve_type_vars_with_obligations(expr_ty);
debug!("coercion::try({:?}: {:?} -> {:?})", expr, source, target);

let cause = self.cause(expr.span, ObligationCauseCode::ExprAssignable);
let coerce = Coerce::new(self, cause);
let coerce = Coerce::new(self, cause, allow_two_phase);
let ok = self.commit_if_ok(|_| coerce.coerce(source, target))?;

let (adjustments, _) = self.register_infer_ok_obligations(ok);
Expand All @@ -771,7 +781,8 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
debug!("coercion::can({:?} -> {:?})", source, target);

let cause = self.cause(syntax_pos::DUMMY_SP, ObligationCauseCode::ExprAssignable);
let coerce = Coerce::new(self, cause);
// We don't ever need two-phase here since we throw out the result of the coercion
let coerce = Coerce::new(self, cause, false);
self.probe(|_| coerce.coerce(source, target)).is_ok()
}

Expand Down Expand Up @@ -840,7 +851,12 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
return Ok(fn_ptr);
}

let mut coerce = Coerce::new(self, cause.clone());
// Configure a Coerce instance to compute the LUB.
// We don't allow two-phase borrows on any autorefs this creates since we
// probably aren't processing function arguments here and even if we were,
// they're going to get autorefed again anyway and we can apply 2-phase borrows
// at that time.
let mut coerce = Coerce::new(self, cause.clone(), false);
coerce.use_lub = true;

// First try to coerce the new expression to the type of the previous ones,
Expand Down Expand Up @@ -1106,7 +1122,8 @@ impl<'gcx, 'tcx, 'exprs, E> CoerceMany<'gcx, 'tcx, 'exprs, E>
if self.pushed == 0 {
// Special-case the first expression we are coercing.
// To be honest, I'm not entirely sure why we do this.
fcx.try_coerce(expression, expression_ty, self.expected_ty)
// We don't allow two-phase borrows, see comment in try_find_coercion_lub for why
fcx.try_coerce(expression, expression_ty, self.expected_ty, false)
} else {
match self.expressions {
Expressions::Dynamic(ref exprs) =>
Expand Down
10 changes: 6 additions & 4 deletions src/librustc_typeck/check/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
pub fn demand_coerce(&self,
expr: &hir::Expr,
checked_ty: Ty<'tcx>,
expected: Ty<'tcx>)
expected: Ty<'tcx>,
allow_two_phase: bool)
-> Ty<'tcx> {
let (ty, err) = self.demand_coerce_diag(expr, checked_ty, expected);
let (ty, err) = self.demand_coerce_diag(expr, checked_ty, expected, allow_two_phase);
if let Some(mut err) = err {
err.emit();
}
Expand All @@ -96,11 +97,12 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
pub fn demand_coerce_diag(&self,
expr: &hir::Expr,
checked_ty: Ty<'tcx>,
expected: Ty<'tcx>)
expected: Ty<'tcx>,
allow_two_phase: bool)
-> (Ty<'tcx>, Option<DiagnosticBuilder<'tcx>>) {
let expected = self.resolve_type_vars_with_obligations(expected);

let e = match self.try_coerce(expr, checked_ty, expected) {
let e = match self.try_coerce(expr, checked_ty, expected, allow_two_phase) {
Ok(ty) => return (ty, None),
Err(e) => e
};
Expand Down
7 changes: 4 additions & 3 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2649,7 +2649,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
// to, which is `expected_ty` if `rvalue_hint` returns an
// `ExpectHasType(expected_ty)`, or the `formal_ty` otherwise.
let coerce_ty = expected.and_then(|e| e.only_has_type(self));
self.demand_coerce(&arg, checked_ty, coerce_ty.unwrap_or(formal_ty));
self.demand_coerce(&arg, checked_ty, coerce_ty.unwrap_or(formal_ty), true);

// 3. Relate the expected type and the formal one,
// if the expected type was used for the coercion.
Expand Down Expand Up @@ -2812,7 +2812,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
expr,
ExpectHasType(expected),
needs);
self.demand_coerce(expr, ty, expected)
self.demand_coerce(expr, ty, expected, false)
}

fn check_expr_with_hint(&self, expr: &'gcx hir::Expr,
Expand Down Expand Up @@ -4112,7 +4112,8 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
let base_t = self.structurally_resolved_type(expr.span, base_t);
match self.lookup_indexing(expr, base, base_t, idx_t, needs) {
Some((index_ty, element_ty)) => {
self.demand_coerce(idx, idx_t, index_ty);
// two-phase not needed because index_ty is never mutable
self.demand_coerce(idx, idx_t, index_ty, false);
element_ty
}
None => {
Expand Down
49 changes: 31 additions & 18 deletions src/test/compile-fail/borrowck/two-phase-nonrecv-autoref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// revisions: lxl nll
// revisions: ast lxl nll
//[ast]compile-flags:
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll

Expand All @@ -33,17 +34,14 @@

use std::ops::{Index, IndexMut};

// This is case outlined by Niko that we want to ensure we reject
// (at least initially).

fn foo(x: &mut u32, y: u32) {
*x += y;
}

fn deref_coercion(x: &mut u32) {
foo(x, *x);
//[lxl]~^ ERROR cannot use `*x` because it was mutably borrowed [E0503]
//[nll]~^^ ERROR cannot use `*x` because it was mutably borrowed [E0503]
//[ast]~^ ERROR cannot use `*x` because it was mutably borrowed [E0503]
// Above error is a known limitation of AST borrowck
}

// While adding a flag to adjustments (indicating whether they
Expand Down Expand Up @@ -74,22 +72,25 @@ fn overloaded_call_traits() {
//[lxl]~^ ERROR cannot borrow `*f` as mutable more than once at a time
//[nll]~^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[g2p]~^^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[ast]~^^^^ ERROR cannot borrow `*f` as mutable more than once at a time
}
fn twice_ten_si<F: Fn(i32) -> i32>(f: &mut F) {
f(f(10));
}
fn twice_ten_so<F: FnOnce(i32) -> i32>(f: Box<F>) {
f(f(10));
//[lxl]~^ ERROR use of moved value: `*f`
//[nll]~^^ ERROR use of moved value: `*f`
//[g2p]~^^^ ERROR use of moved value: `*f`
//[lxl]~^ ERROR use of moved value: `*f`
//[nll]~^^ ERROR use of moved value: `*f`
//[g2p]~^^^ ERROR use of moved value: `*f`
//[ast]~^^^^ ERROR use of moved value: `*f`
}

fn twice_ten_om(f: &mut FnMut(i32) -> i32) {
f(f(10));
//[lxl]~^ ERROR cannot borrow `*f` as mutable more than once at a time
//[lxl]~^ ERROR cannot borrow `*f` as mutable more than once at a time
//[nll]~^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[g2p]~^^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[g2p]~^^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[ast]~^^^^ ERROR cannot borrow `*f` as mutable more than once at a time
}
fn twice_ten_oi(f: &mut Fn(i32) -> i32) {
f(f(10));
Expand All @@ -105,6 +106,7 @@ fn overloaded_call_traits() {
//[g2p]~^^^^^^^ ERROR cannot move a value of type
//[g2p]~^^^^^^^^ ERROR cannot move a value of type
//[g2p]~^^^^^^^^^ ERROR use of moved value: `*f`
//[ast]~^^^^^^^^^^ ERROR use of moved value: `*f`
}

twice_ten_sm(&mut |x| x + 1);
Expand Down Expand Up @@ -142,12 +144,15 @@ fn coerce_unsized() {

// This is not okay.
double_access(&mut a, &a);
//[lxl]~^ ERROR cannot borrow `a` as immutable because it is also borrowed as mutable [E0502]
//[nll]~^^ ERROR cannot borrow `a` as immutable because it is also borrowed as mutable [E0502]
//[g2p]~^^^ ERROR cannot borrow `a` as immutable because it is also borrowed as mutable [E0502]
//[lxl]~^ ERROR cannot borrow `a` as immutable because it is also borrowed as mutable [E0502]
//[nll]~^^ ERROR cannot borrow `a` as immutable because it is also borrowed as mutable [E0502]
//[g2p]~^^^ ERROR cannot borrow `a` as immutable because it is also borrowed as mutable [E0502]
//[ast]~^^^^ ERROR cannot borrow `a` as immutable because it is also borrowed as mutable [E0502]

// But this is okay.
a.m(a.i(10));
//[ast]~^ ERROR cannot borrow `a` as immutable because it is also borrowed as mutable [E0502]
// Above error is an expected limitation of AST borrowck
}

struct I(i32);
Expand All @@ -168,21 +173,25 @@ impl IndexMut<i32> for I {
fn coerce_index_op() {
let mut i = I(10);
i[i[3]] = 4;
//[lxl]~^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]
//[nll]~^^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]
//[lxl]~^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]
//[nll]~^^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]
//[ast]~^^^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]

i[3] = i[4];

i[i[3]] = i[4];
//[lxl]~^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]
//[nll]~^^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]
//[lxl]~^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]
//[nll]~^^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]
//[ast]~^^^ ERROR cannot borrow `i` as immutable because it is also borrowed as mutable [E0502]
}

fn main() {

// As a reminder, this is the basic case we want to ensure we handle.
let mut v = vec![1, 2, 3];
v.push(v.len());
//[ast]~^ ERROR cannot borrow `v` as immutable because it is also borrowed as mutable [E0502]
// Error above is an expected limitation of AST borrowck

// (as a rule, pnkfelix does not like to write tests with dead code.)

Expand All @@ -192,9 +201,13 @@ fn main() {

let mut s = S;
s.m(s.i(10));
//[ast]~^ ERROR cannot borrow `s` as immutable because it is also borrowed as mutable [E0502]
// Error above is an expected limitation of AST borrowck

let mut t = T;
t.m(t.i(10));
//[ast]~^ ERROR cannot borrow `t` as immutable because it is also borrowed as mutable [E0502]
// Error above is an expected limitation of AST borrowck

coerce_unsized();
coerce_index_op();
Expand Down
29 changes: 29 additions & 0 deletions src/test/ui/borrowck/two-phase-method-receivers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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

// run-pass

struct Foo<'a> {
x: &'a i32
}

impl<'a> Foo<'a> {
fn method(&mut self, _: &i32) {
}
}

fn main() {
let a = &mut Foo { x: &22 };
Foo::method(a, a.x);
}