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

two-phase borrows are causing #[feature(nll)] to reject a mutable borrow incorrectly #48070

Closed
nikomatsakis opened this issue Feb 8, 2018 · 19 comments · Fixed by #48988
Closed
Labels
A-NLL Area: Non-lexical lifetimes (NLL) NLL-complete Working towards the "valid code works" goal T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@nikomatsakis
Copy link
Contributor

In this example, the NLL checker incorrectly judges foo to be borrowed more than once:

#![feature(nll)]

struct Foo {
    x: u32
}

impl Foo {
    fn twiddle(&mut self) -> &mut Self { self }
    fn twaddle(&mut self) -> &mut Self { self }
    fn emit(&mut self) {
        self.x += 1;
    }
}

fn main() {
    let mut foo = Foo { x: 0 };
    match 22 {
        22 => &mut foo,
        44 => foo.twiddle(),
        _ => foo.twaddle(),
    }.emit();
}

I get this error:

error[E0499]: cannot borrow `foo` as mutable more than once at a time
  --> src/main.rs:17:5
   |
17 | /     match 22 {
18 | |         22 => &mut foo,
19 | |         44 => foo.twiddle(),
   | |               --- first mutable borrow occurs here
20 | |         _ => foo.twaddle(),
21 | |     }.emit();
   | |_____^ second mutable borrow occurs here

This works without NLL. This was found while trying to bootstrap rustc with NLL enabled, where it manifests as:

error[E0499]: cannot borrow `diag_builder` as mutable more than once at a time
   --> src/librustc/lint/levels.rs:304:13
    |
304 | /             match forbid_src {
305 | |                 LintSource::Default => &mut diag_builder,
306 | |                 LintSource::Node(_, forbid_source_span) => {
307 | |                     diag_builder.span_label(forbid_source_span,
...   |
311 | |                     diag_builder.note("`forbid` lint level was set on command line")
    | |                     ------------ first mutable borrow occurs here
312 | |                 }
313 | |             }.emit();
    | |_____________^ second mutable borrow occurs here
@nikomatsakis nikomatsakis added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-NLL Area: Non-lexical lifetimes (NLL) WG-compiler-nll labels Feb 8, 2018
@nikomatsakis nikomatsakis added this to the NLL: Valid code works milestone Feb 8, 2018
@nikomatsakis
Copy link
Contributor Author

cc @rust-lang/wg-compiler-nll

@pnkfelix
Copy link
Member

pnkfelix commented Feb 8, 2018

This is a case where our debug flags are very helpful for isolating the cause of a bug like this.

  • Step 1: Remove #[feature(nll)] from the example.
  • Step 2: Incrementally work way to recreating effect of feature(nll); my hypothesis is (well, was) that this bug was injected by either mir-borrowck or, somehow, by NLL itself... lets test that hypothesis:
% rustc issue-48070.rs                          # sanity check
% rustc issue-48070.rs -Z borrowck=mir          # mir-borrowck (lexically via EndRegion)
% rustc issue-48070.rs -Z borrowck=mir -Z nll   # okay, then it must be NLL...?
% 

I was scratching my head at this point... and then it hit me:

% rustc issue-48070.rs -Z borrowck=mir -Z nll -Z two-phase-borrows
error[E0499]: cannot borrow `foo` as mutable more than once at a time
  --> issue-48070.rs:15:5
   |
15 | /     match 22 {
16 | |         22 => &mut foo,
17 | |         44 => foo.twiddle(),
   | |               --- first mutable borrow occurs here
18 | |         _ => foo.twaddle(),
19 | |     }.emit();
   | |_____^ second mutable borrow occurs here

error: aborting due to previous error

% 

@pnkfelix pnkfelix changed the title mutable borrow two-phase borrows are causing feature(nll) to reject a mutable borrow incorrectly Feb 8, 2018
@pnkfelix pnkfelix changed the title two-phase borrows are causing feature(nll) to reject a mutable borrow incorrectly two-phase borrows are causing #[feature(nll)] to reject a mutable borrow incorrectly Feb 8, 2018
@nikomatsakis
Copy link
Contributor Author

@pnkfelix ah, good catch! I guess this is because those mutable borrows are "activating" at this later point? Fascinating.

@KiChjang
Copy link
Member

KiChjang commented Feb 11, 2018

I'm going to take a look at this bug.

@KiChjang
Copy link
Member

KiChjang commented Feb 12, 2018

Here's the MIR dump I managed to extract:

DEBUG:<unknown>: access_place: logging error place_span=`(_1, .\issue-48070.rs:15:5: 19:6)` kind=`(Deep, Activation(MutableBorrow(Mut { allow_two_phase_borrow: false }), bw0))`
DEBUG:<unknown>: check_access_permissions((*_4), Reservation(MutableBorrow(Mut { allow_two_phase_borrow: true })), No)
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: check_if_path_is_moved part1 place: (*_4)
DEBUG:<unknown>: check_if_path_is_moved part2 place: (*_4)
DEBUG:<unknown>: check_access_permissions(_3, Write(Mutate), ExceptUpvars)
DEBUG:<unknown>: places_conflict(_1,_3,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_3,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_3,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_3,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_3,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: check_if_reassignment_to_immutable_state(_3)
DEBUG:<unknown>: MirBorrowckCtxt::process_terminator(bb9[1], Terminator { source_info: SourceInfo { span: .\issue-48070.rs:15:5: 19:13, scope: scope[1] }, kind: _2 = const Foo::emit(move _3) -> [return: bb12, unwind: bb1] }): borrows in effect: [&mut _1, &mut _1@active, &mut _1, &mut _1@active, &mut _1, &mut _1@active, &mut (*_4)] borrows generated: [&mut (*_4)] inits: [_1, _3, _4, _5] uninits: [_0, _2, _6, _7, _8, _9] move_out: [mp7@bb3[3], mp9@bb4[3]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp4@.\issue-48070.rs:16:15: 16:23 (Deep), mp3@.\issue-48070.rs:15:5: 19:6 (Deep), mp4@.\issue-48070.rs:17:15: 17:28 (Deep), mp4@.\issue-48070.rs:18:14: 18:27 (Deep)]
DEBUG:<unknown>: check_access_permissions((*_4), Activation(MutableBorrow(Mut { allow_two_phase_borrow: true }), bw3), No)
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict((*_4),(*_4),Deep)
DEBUG:<unknown>: places_conflict: components [_4, (*_4)] / [_4, (*_4)]
DEBUG:<unknown>: places_conflict: Some(_4) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-OR-EQ-LOCAL
DEBUG:<unknown>: places_conflict: Some((*_4)) vs. Some((*_4))
DEBUG:<unknown>: place_element_conflict: DISJOINT-OR-EQ-DEREF
DEBUG:<unknown>: places_conflict: None vs. None
DEBUG:<unknown>: places_conflict: full borrow, CONFLICT
DEBUG:<unknown>: each_borrow_involving_path: ra6 @ BorrowData { location: bb9[0], kind: Mut { allow_two_phase_borrow: true }, region: '_#5r, borrowed_place: (*_4), assigned_place: _3 } vs. (*_4)/Deep
DEBUG:<unknown>: check_access_for_conflict place_span: ((*_4), .\issue-48070.rs:15:5: 19:13) sd: Deep rw: Activation(MutableBorrow(Mut { allow_two_phase_borrow: true }), bw3) skipping (ra6, BorrowData { location: bb9[0], kind: Mut { allow_two_phase_borrow: true }, region: '_#5r, borrowed_place: (*_4), assigned_place: _3 }) b/c activation of same borrow_index: bw3
DEBUG:<unknown>: check_access_permissions(_3, Write(Move), Yes)
DEBUG:<unknown>: places_conflict(_1,_3,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_3,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_3,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_3,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_3,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_3,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_3]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict((*_4),_3,Deep)
DEBUG:<unknown>: places_conflict: components [_4, (*_4)] / [_3]
DEBUG:<unknown>: places_conflict: Some(_4) vs. Some(_3)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: check_if_path_is_moved part1 place: _3
DEBUG:<unknown>: check_if_path_is_moved part2 place: _3
DEBUG:<unknown>: check_access_permissions(_2, Write(Mutate), ExceptUpvars)
DEBUG:<unknown>: places_conflict(_1,_2,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_2]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_2)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_2,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_2]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_2)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_2,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_2]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_2)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_2,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_2]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_2)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_2,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_2]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_2)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_2,Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_2]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_2)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict((*_4),_2,Deep)
DEBUG:<unknown>: places_conflict: components [_4, (*_4)] / [_2]
DEBUG:<unknown>: places_conflict: Some(_4) vs. Some(_2)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: check_if_reassignment_to_immutable_state(_2)
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb10[0], _4 = &mut (*_6)): borrows in effect: [&mut _1, &mut _1@active] borrows generated: [&mut (*_6)] inits: [_1, _5, _6] uninits: [_0, _2, _3, _4, _7, _8, _9] move_out: [mp7@bb3[3]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp7@.\issue-48070.rs:17:15: 17:18 (Deep), mp6@.\issue-48070.rs:17:15: 17:28 (NonPanicPathOnly)]
DEBUG:<unknown>: check_access_permissions((*_6), Write(MutableBorrow(Mut { allow_two_phase_borrow: false })), No)
DEBUG:<unknown>: places_conflict(_1,(*_6),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_6, (*_6)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_6)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_6),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_6, (*_6)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_6)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: check_if_path_is_moved part1 place: (*_6)
DEBUG:<unknown>: check_if_path_is_moved part2 place: (*_6)
DEBUG:<unknown>: check_access_permissions(_4, Write(Mutate), ExceptUpvars)
DEBUG:<unknown>: places_conflict(_1,_4,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_4]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_4,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_4]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: check_if_reassignment_to_immutable_state(_4)
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb10[1], StorageDead(_6)): borrows in effect: [&mut _1, &mut _1@active, &mut (*_6)] borrows generated: [] inits: [_1, _4, _5, _6] uninits: [_0, _2, _3, _7, _8, _9] move_out: [mp7@bb3[3]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp7@.\issue-48070.rs:17:15: 17:18 (Deep), mp6@.\issue-48070.rs:17:15: 17:28 (NonPanicPathOnly), mp4@.\issue-48070.rs:17:15: 17:28 (Deep)]
DEBUG:<unknown>: check_access_permissions(_6, Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: places_conflict(_1,_6,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_6]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_6)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_6,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_6]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_6)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict((*_6),_6,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_6, (*_6)] / [_6]
DEBUG:<unknown>: places_conflict: Some(_6) vs. Some(_6)
DEBUG:<unknown>: place_element_conflict: DISJOINT-OR-EQ-LOCAL
DEBUG:<unknown>: places_conflict: Some((*_6)) vs. None
DEBUG:<unknown>: places_conflict: shallow access behind ptr
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb10[2], StorageDead(_7)): borrows in effect: [&mut _1, &mut _1@active] borrows generated: [] inits: [_1, _4, _5] uninits: [_0, _2, _3, _6, _7, _8, _9] move_out: [mp7@bb3[3]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp7@.\issue-48070.rs:17:15: 17:18 (Deep), mp4@.\issue-48070.rs:17:15: 17:28 (Deep)]
DEBUG:<unknown>: check_access_permissions(_7, Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: places_conflict(_1,_7,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_7]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_7)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_7,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_7]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_7)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: MirBorrowckCtxt::process_terminator(bb10[3], Terminator { source_info: SourceInfo { span: .\issue-48070.rs:15:5: 19:6, scope: scope[1] }, kind: goto -> bb9 }): borrows in effect: [&mut _1, &mut _1@active] borrows generated: [] inits: [_1, _4, _5] uninits: [_0, _2, _3, _6, _7, _8, _9] move_out: [mp7@bb3[3]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp4@.\issue-48070.rs:17:15: 17:28 (Deep)]
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb11[0], _4 = &mut (*_8)): borrows in effect: [&mut _1, &mut _1@active] borrows generated: [&mut (*_8)] inits: [_1, _5, _8] uninits: [_0, _2, _3, _4, _6, _7, _9] move_out: [mp9@bb4[3]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp9@.\issue-48070.rs:18:14: 18:17 (Deep), mp8@.\issue-48070.rs:18:14: 18:27 (NonPanicPathOnly)]
DEBUG:<unknown>: check_access_permissions((*_8), Write(MutableBorrow(Mut { allow_two_phase_borrow: false })), No)
DEBUG:<unknown>: places_conflict(_1,(*_8),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_8, (*_8)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_8)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,(*_8),Deep)
DEBUG:<unknown>: places_conflict: components [_1] / [_8, (*_8)]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_8)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: check_if_path_is_moved part1 place: (*_8)
DEBUG:<unknown>: check_if_path_is_moved part2 place: (*_8)
DEBUG:<unknown>: check_access_permissions(_4, Write(Mutate), ExceptUpvars)
DEBUG:<unknown>: places_conflict(_1,_4,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_4]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_4,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_4]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_4)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: check_if_reassignment_to_immutable_state(_4)
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb11[1], StorageDead(_8)): borrows in effect: [&mut _1, &mut _1@active, &mut (*_8)] borrows generated: [] inits: [_1, _4, _5, _8] uninits: [_0, _2, _3, _6, _7, _9] move_out: [mp9@bb4[3]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp9@.\issue-48070.rs:18:14: 18:17 (Deep), mp8@.\issue-48070.rs:18:14: 18:27 (NonPanicPathOnly), mp4@.\issue-48070.rs:18:14: 18:27 (Deep)]
DEBUG:<unknown>: check_access_permissions(_8, Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: places_conflict(_1,_8,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_8]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_8)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_8,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_8]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_8)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict((*_8),_8,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_8, (*_8)] / [_8]
DEBUG:<unknown>: places_conflict: Some(_8) vs. Some(_8)
DEBUG:<unknown>: place_element_conflict: DISJOINT-OR-EQ-LOCAL
DEBUG:<unknown>: places_conflict: Some((*_8)) vs. None
DEBUG:<unknown>: places_conflict: shallow access behind ptr
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb11[2], StorageDead(_9)): borrows in effect: [&mut _1, &mut _1@active] borrows generated: [] inits: [_1, _4, _5] uninits: [_0, _2, _3, _6, _7, _8, _9] move_out: [mp9@bb4[3]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp9@.\issue-48070.rs:18:14: 18:17 (Deep), mp4@.\issue-48070.rs:18:14: 18:27 (Deep)]
DEBUG:<unknown>: check_access_permissions(_9, Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: places_conflict(_1,_9,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_9]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_9)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: places_conflict(_1,_9,Shallow(None))
DEBUG:<unknown>: places_conflict: components [_1] / [_9]
DEBUG:<unknown>: places_conflict: Some(_1) vs. Some(_9)
DEBUG:<unknown>: place_element_conflict: DISJOINT-LOCAL
DEBUG:<unknown>: places_conflict: disjoint
DEBUG:<unknown>: MirBorrowckCtxt::process_terminator(bb11[3], Terminator { source_info: SourceInfo { span: .\issue-48070.rs:15:5: 19:6, scope: scope[1] }, kind: goto -> bb9 }): borrows in effect: [&mut _1, &mut _1@active] borrows generated: [] inits: [_1, _4, _5] uninits: [_0, _2, _3, _6, _7, _8, _9] move_out: [mp9@bb4[3]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp4@.\issue-48070.rs:18:14: 18:27 (Deep)]
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb12[0], nop): borrows in effect: [] borrows generated: [] inits: [_1, _2, _4, _5] uninits: [_0, _3, _6, _7, _8, _9] move_out: [mp7@bb3[3], mp9@bb4[3], mp3@bb9[1]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp4@.\issue-48070.rs:16:15: 16:23 (Deep), mp3@.\issue-48070.rs:15:5: 19:6 (Deep), mp2@.\issue-48070.rs:15:5: 19:13 (NonPanicPathOnly), mp4@.\issue-48070.rs:17:15: 17:28 (Deep), mp4@.\issue-48070.rs:18:14: 18:27 (Deep)]
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb12[1], StorageDead(_3)): borrows in effect: [] borrows generated: [] inits: [_1, _2, _4, _5] uninits: [_0, _3, _6, _7, _8, _9] move_out: [mp7@bb3[3], mp9@bb4[3], mp3@bb9[1]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp4@.\issue-48070.rs:16:15: 16:23 (Deep), mp3@.\issue-48070.rs:15:5: 19:6 (Deep), mp2@.\issue-48070.rs:15:5: 19:13 (NonPanicPathOnly), mp4@.\issue-48070.rs:17:15: 17:28 (Deep), mp4@.\issue-48070.rs:18:14: 18:27 (Deep)]
DEBUG:<unknown>: check_access_permissions(_3, Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb12[2], StorageDead(_4)): borrows in effect: [] borrows generated: [] inits: [_1, _2, _4, _5] uninits: [_0, _3, _6, _7, _8, _9] move_out: [mp7@bb3[3], mp9@bb4[3], mp3@bb9[1]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp4@.\issue-48070.rs:16:15: 16:23 (Deep), mp2@.\issue-48070.rs:15:5: 19:13 (NonPanicPathOnly), mp4@.\issue-48070.rs:17:15: 17:28 (Deep), mp4@.\issue-48070.rs:18:14: 18:27 (Deep)]
DEBUG:<unknown>: check_access_permissions(_4, Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb12[3], StorageDead(_5)): borrows in effect: [] borrows generated: [] inits: [_1, _2, _5] uninits: [_0, _3, _4, _6, _7, _8, _9] move_out: [mp7@bb3[3], mp9@bb4[3], mp3@bb9[1]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp5@.\issue-48070.rs:15:11: 15:13 (Deep), mp2@.\issue-48070.rs:15:5: 19:13 (NonPanicPathOnly)]
DEBUG:<unknown>: check_access_permissions(_5, Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb12[4], _0 = ()): borrows in effect: [] borrows generated: [] inits: [_1, _2] uninits: [_0, _3, _4, _5, _6, _7, _8, _9] move_out: [mp7@bb3[3], mp9@bb4[3], mp3@bb9[1]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp2@.\issue-48070.rs:15:5: 19:13 (NonPanicPathOnly)]
DEBUG:<unknown>: check_access_permissions(_0, Write(Mutate), ExceptUpvars)
DEBUG:<unknown>: check_if_reassignment_to_immutable_state(_0)
DEBUG:<unknown>: MirBorrowckCtxt::process_statement(bb12[5], StorageDead(_1)): borrows in effect: [] borrows generated: [] inits: [_0, _1, _2] uninits: [_3, _4, _5, _6, _7, _8, _9] move_out: [mp7@bb3[3], mp9@bb4[3], mp3@bb9[1]] ever_init: [mp1@.\issue-48070.rs:14:19: 14:31 (Deep), mp2@.\issue-48070.rs:15:5: 19:13 (NonPanicPathOnly), mp0@.\issue-48070.rs:13:11: 20:2 (Deep)]
DEBUG:<unknown>: check_access_permissions(_1, Write(StorageDeadOrDrop), Yes)
DEBUG:<unknown>: MirBorrowckCtxt::process_terminator(bb12[6], Terminator { source_info: SourceInfo { span: .\issue-48070.rs:20:2: 20:2, scope: scope[0] }, kind: return }): borrows in effect: [] borrows generated: [] inits: [_0, _2] uninits: [_1, _3, _4, _5, _6, _7, _8, _9] move_out: [mp7@bb3[3], mp9@bb4[3], mp3@bb9[1]] ever_init: [mp2@.\issue-48070.rs:15:5: 19:13 (NonPanicPathOnly), mp0@.\issue-48070.rs:13:11: 20:2 (Deep)]
DEBUG:<unknown>: mir_borrowck done

And its corresponding MIR dump looks like the following:

// MIR for `main`
// source = MirSource { def_id: DefId(0/0:8 ~ issue_48070[317d]::main[0]), promoted: None }
// pass_name = QualifyAndPromoteConstants
// disambiguator = before

fn main() -> (){
    let mut _0: ();                      // return place
    scope 1 {
        let mut _1: Foo;                 // "foo" in scope 1 at .\issue-48070.rs:14:9: 14:16
    }
    scope 2 {
    }
    let mut _2: ();
    let mut _3: &mut Foo;
    let mut _4: &mut Foo;
    let mut _5: i32;
    let mut _6: &mut Foo;
    let mut _7: &mut Foo;
    let mut _8: &mut Foo;
    let mut _9: &mut Foo;

    bb0: {                              
        StorageLive(_1);                 // bb0[0]: scope 0 at .\issue-48070.rs:14:9: 14:16
        _1 = Foo { x: const 0u32 };      // bb0[1]: scope 0 at .\issue-48070.rs:14:19: 14:31
                                         // ty::Const
                                         // + ty: u32
                                         // + val: Integral(U32(0))
                                         // mir::Constant
                                         // + span: .\issue-48070.rs:14:28: 14:29
                                         // + ty: u32
                                         // + literal: const 0u32
        StorageLive(_3);                 // bb0[2]: scope 1 at .\issue-48070.rs:15:5: 19:6
        StorageLive(_4);                 // bb0[3]: scope 1 at .\issue-48070.rs:15:5: 19:6
        StorageLive(_5);                 // bb0[4]: scope 1 at .\issue-48070.rs:15:11: 15:13
        _5 = const 22i32;                // bb0[5]: scope 1 at .\issue-48070.rs:15:11: 15:13
                                         // ty::Const
                                         // + ty: i32
                                         // + val: Integral(I32(22))
                                         // mir::Constant
                                         // + span: .\issue-48070.rs:15:11: 15:13
                                         // + ty: i32
                                         // + literal: const 22i32
        switchInt(_5) -> [22i32: bb5, 44i32: bb6, otherwise: bb7]; // bb0[6]: scope 1 at .\issue-48070.rs:16:9: 16:11
    }

    bb1: {                               // cleanup
        resume;                          // bb1[0]: scope 0 at .\issue-48070.rs:13:1: 20:2
    }

    bb2: {                              
        _4 = &mut _1;                    // bb2[0]: scope 1 at .\issue-48070.rs:16:15: 16:23
        goto -> bb9;                     // bb2[1]: scope 1 at .\issue-48070.rs:15:5: 19:6
    }

    bb3: {                              
        StorageLive(_6);                 // bb3[0]: scope 1 at .\issue-48070.rs:17:15: 17:28
        StorageLive(_7);                 // bb3[1]: scope 1 at .\issue-48070.rs:17:15: 17:18
        _7 = &mut _1;                    // bb3[2]: scope 1 at .\issue-48070.rs:17:15: 17:18
        _6 = const Foo::twiddle(move _7) -> [return: bb10, unwind: bb1]; // bb3[3]: scope 1 at .\issue-48070.rs:17:15: 17:28
                                         // ty::Const
                                         // + ty: for<'r> fn(&'r mut Foo) -> &'r mut Foo {Foo::twiddle}
                                         // + val: Function(DefId(0/0:5 ~ issue_48070[317d]::{{impl}}[0]::twiddle[0]), Slice([]))
                                         // mir::Constant
                                         // + span: .\issue-48070.rs:17:15: 17:28
                                         // + ty: for<'r> fn(&'r mut Foo) -> &'r mut Foo {Foo::twiddle}
                                         // + literal: const Foo::twiddle
    }

    bb4: {                              
        StorageLive(_8);                 // bb4[0]: scope 1 at .\issue-48070.rs:18:14: 18:27
        StorageLive(_9);                 // bb4[1]: scope 1 at .\issue-48070.rs:18:14: 18:17
        _9 = &mut _1;                    // bb4[2]: scope 1 at .\issue-48070.rs:18:14: 18:17
        _8 = const Foo::twaddle(move _9) -> [return: bb11, unwind: bb1]; // bb4[3]: scope 1 at .\issue-48070.rs:18:14: 18:27
                                         // ty::Const
                                         // + ty: for<'r> fn(&'r mut Foo) -> &'r mut Foo {Foo::twaddle}
                                         // + val: Function(DefId(0/0:6 ~ issue_48070[317d]::{{impl}}[0]::twaddle[0]), Slice([]))
                                         // mir::Constant
                                         // + span: .\issue-48070.rs:18:14: 18:27
                                         // + ty: for<'r> fn(&'r mut Foo) -> &'r mut Foo {Foo::twaddle}
                                         // + literal: const Foo::twaddle
    }

    bb5: {                              
        falseEdges -> [real: bb2, imaginary: bb6]; // bb5[0]: scope 1 at .\issue-48070.rs:16:9: 16:11
    }

    bb6: {                              
        falseEdges -> [real: bb3, imaginary: bb7]; // bb6[0]: scope 1 at .\issue-48070.rs:17:9: 17:11
    }

    bb7: {                              
        falseEdges -> [real: bb4, imaginary: bb8]; // bb7[0]: scope 1 at .\issue-48070.rs:18:9: 18:10
    }

    bb8: {                              
        unreachable;                     // bb8[0]: scope 1 at .\issue-48070.rs:15:5: 19:6
    }

    bb9: {                              
        _3 = &mut (*_4);                 // bb9[0]: scope 1 at .\issue-48070.rs:15:5: 19:6
        _2 = const Foo::emit(move _3) -> [return: bb12, unwind: bb1]; // bb9[1]: scope 1 at .\issue-48070.rs:15:5: 19:13
                                         // ty::Const
                                         // + ty: for<'r> fn(&'r mut Foo) {Foo::emit}
                                         // + val: Function(DefId(0/0:7 ~ issue_48070[317d]::{{impl}}[0]::emit[0]), Slice([]))
                                         // mir::Constant
                                         // + span: .\issue-48070.rs:15:5: 19:13
                                         // + ty: for<'r> fn(&'r mut Foo) {Foo::emit}
                                         // + literal: const Foo::emit
    }

    bb10: {                             
        _4 = &mut (*_6);                 // bb10[0]: scope 1 at .\issue-48070.rs:17:15: 17:28
        StorageDead(_6);                 // bb10[1]: scope 1 at .\issue-48070.rs:17:27: 17:28
        StorageDead(_7);                 // bb10[2]: scope 1 at .\issue-48070.rs:17:27: 17:28
        goto -> bb9;                     // bb10[3]: scope 1 at .\issue-48070.rs:15:5: 19:6
    }

    bb11: {                             
        _4 = &mut (*_8);                 // bb11[0]: scope 1 at .\issue-48070.rs:18:14: 18:27
        StorageDead(_8);                 // bb11[1]: scope 1 at .\issue-48070.rs:18:26: 18:27
        StorageDead(_9);                 // bb11[2]: scope 1 at .\issue-48070.rs:18:26: 18:27
        goto -> bb9;                     // bb11[3]: scope 1 at .\issue-48070.rs:15:5: 19:6
    }

    bb12: {                             
        EndRegion();                     // bb12[0]: scope 1 at .\issue-48070.rs:15:5: 19:13
        StorageDead(_3);                 // bb12[1]: scope 1 at .\issue-48070.rs:19:12: 19:13
        StorageDead(_4);                 // bb12[2]: scope 1 at .\issue-48070.rs:19:13: 19:14
        StorageDead(_5);                 // bb12[3]: scope 1 at .\issue-48070.rs:19:13: 19:14
        _0 = ();                         // bb12[4]: scope 0 at .\issue-48070.rs:13:11: 20:2
        StorageDead(_1);                 // bb12[5]: scope 0 at .\issue-48070.rs:20:1: 20:2
        return;                          // bb12[6]: scope 0 at .\issue-48070.rs:20:2: 20:2
    }
}

It's probably not that important to look at the MIR dump, because the second line of the RUST_LOG output contained this:

DEBUG:<unknown>: check_access_permissions((*_4), Reservation(MutableBorrow(Mut { allow_two_phase_borrow: true })), No)

Where the last parameter is the value of is_local_mutation_allowed, which strikes me as odd since a Reserved borrow should be able to be mutated... Oh wait, perhaps I'm wrong? Is this supposed to be activated before being mutated?

Note that _4 is a temporary created by MIR that will be used to call .emit() upon.

@nikomatsakis
Copy link
Contributor Author

@KiChjang

I think what is happening is not that surprising. I don't think it's related to the LocalMutationIsAllowed flag, really. @pnkfelix can prob confirm, but I think it goes something like this.

Let's simplify to two arms. I think you'll find this has the same error:

fn main() {
    let mut foo = Foo { x: 0 };
    match 22 {
        22 => &mut foo, // borrow B1
        _ => foo.twaddle(), // borrow B2
    }.emit();
}

I've also labeled the two &mut borrows that occur. Now, the control-flow is something like this:

A --+--> `TMP1 = &mut foo` (B1) ---------------------------------+-> `emit(TMP1)`
       |                                                         ^        ^
       +--> `TMP2 = &mut foo` (B2)` --> `TMP1 = twaddle(TMP2)` --+        |
                                                         ^                |
                                                         |                |
        Borrow B2 activates here, at first use of TMP2 --+                |
                                                                          |
                Borrow B1 activates here, at first use of TMP1 -----------+

As you can see from the diagram, borrow B2 is already active when borrow B1 is activated. This is in part why the initial RFC for two-phase borrows was limited to the temporaries created for method calls: I wanted to ensure that the temporary was (a) assigned once and (b) that assigned was postdominated by a use which was dominated by the one assignment.

@nikomatsakis
Copy link
Contributor Author

I'm a bit surprised though that this doesn't work now. Maybe @pnkfelix's PR that limits two-phase borrows hasn't yet made it into nightly? Would be good to verify whether this still activates with the latest master.

@KiChjang
Copy link
Member

So I just rebased against master, and I still run into this exact same bug. @nikomatsakis which PR were you referring to?

@nikomatsakis
Copy link
Contributor Author

@KiChjang I was referring to #47489.

cc @pnkfelix, thoughts?

@pnkfelix
Copy link
Member

pnkfelix commented Feb 13, 2018

Hmm I wonder if #47489 is still too naive? I’ll try to investigate tomorrow

@pnkfelix
Copy link
Member

(I've started looking at the dataflow state, via borrowck_graphviz_postflow. The initial information I'm getting from there is confusing me at best, so I'm not sure yet what conclusion to draw from it. Will report more later once I've delved into it a little more deeply.)

nikomatsakis added a commit to nikomatsakis/rust that referenced this issue Feb 20, 2018
@pnkfelix
Copy link
Member

pnkfelix commented Feb 21, 2018

So, looking at the dataflow graph (after the dataflow has been run, so the entry sets contain the fixed point results), generated via #[rustc_mir(borrowck_graphviz_postflow)], http://svgur.com/s/5bt and also inspecting the debug output immediately before the error is issued: https://gist.github.com/pnkfelix/4d34cac15230d25cfca89a8777b9c088 ...

  • (this data is based on niko's reduced example from this comment

The dataflow is concluding, on entry to bb7, that both of the borrow _8 = &mut _1; (bb3[2]) and the borrow _4 = &mut _1; (bb2[0]) are reserved, and the bb3[2] is also active (since it was used in the invocation of twaddle on the bb3 terminator).

Thus we get an error that it is not valid to have two simultaneous multiple borrows of _1.

One logical step to take next would be to compare against the dataflow that is derived when we do not have two-phase-borrows turned on. I am going to do that in a bit.

@pnkfelix
Copy link
Member

pnkfelix commented Feb 21, 2018

The bug here is probably that we are erroring on having two mutable-borrows of the same path reach the use of _4 in _3 = &mut (*_4); (bb7[0]).

I often get the details wrong; I suspect that the issue is that we should solely be detecting conflicts between mutable borrows of a given path at the point where the borrow is taken (i.e. the points were we do &mut _1). Or to use the two-phase borrow terminology, the point where the borrow is reserved.

Here, we are checking for it at the point where the borrow is activated (the use of _4 in _3 = &mut (*_4);).

And so that explains why we do not see the problem without two-phase borrows turned on. When two-phase borrows are turned off, the check in question is performed at the point of reservation (since activation points are irrelevant when two-phase borrows are turned off).

@nikomatsakis
Copy link
Contributor Author

@pnkfelix I believe that is roughly the same as my diagnosis here, right?

@nikomatsakis
Copy link
Contributor Author

Well, let me read your comments more closely, because I'm confused which borrow is not activating.

@pnkfelix
Copy link
Member

@nikomatsakis there's some differences compared to your diagnosis, but I don't know if they matter. For example, your hypothesized control flow graph has two activations that reach the same control flow point. The actual graph has a reservation on one path (but no activation), and the other path has a reservation and an activation.

@nikomatsakis
Copy link
Contributor Author

So, the thing that is confusing me from the output is that the _4 = &mut _1 borrow appears not to be activating. However, I believe that comes from 22 => &mut foo, and that (per your recent changes) ought not to be two-phase.

(Note: this might also need my fix from #47689)

@pnkfelix
Copy link
Member

An update regarding the previous comment: We discussed further on gitter; to summarize, I think that the intention is that two-phase borrows have no effect on the dataflow -- instead, their effect is meant to be encoded solely in the borrow_check code itself.

Specific to this case, the dataflow only shows a reservation, not an activation, at the statement _4 = &mut _1;, because that is indeed just the initial borrow of _1, not the use of `_4, and its the job of the borrow_check code to actually react properly to that reservation.

@pnkfelix
Copy link
Member

After even further discussion on gitter and also in video chat, @nikomatsakis and @pnkfelix came to the conclusion that the answer here is probably not a surgical fix covering a small number of lines, but rather a slightly larger-scale (but mentorable) effort to redo two-phase borrows so that it more closely matches the original intention of the RFC. See #48431

sapphire-arches added a commit to sapphire-arches/rust that referenced this issue Mar 13, 2018
Resolves rust-lang#48070.
The bug itself was fixed by rust-lang#48770, but that PR didn't add a test for it.
kennytm added a commit to kennytm/rust that referenced this issue Mar 14, 2018
…atsakis

Add a test for rust-lang#48070

Resolves rust-lang#48070.
The bug itself was fixed by rust-lang#48770, but that PR didn't add a test for it.

r? @nikomatsakis
@nikomatsakis nikomatsakis added the NLL-complete Working towards the "valid code works" goal label Mar 14, 2018
nikomatsakis added a commit to nikomatsakis/rust that referenced this issue Mar 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-NLL Area: Non-lexical lifetimes (NLL) NLL-complete Working towards the "valid code works" goal T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants