Skip to content

Commit 67f9c71

Browse files
committed
Auto merge of #52564 - pnkfelix:issue-52126-lhs-of-assign-op-is-invariant, r=eddyb
LHS of assign op is invariant This addresses a bug injected by #45435. That PR changed the way we type-check `LHS <op> RHS` to coerce the LHS to the expected supertype in much the same way that we coerce the RHS. The problem is that when we have a case of `LHS <op>= RHS`, we do not want to coerce to a supertype; we need the type to remain invariant. Otherwise we risk leaking a value with short-lifetimes into a expression context that needs to satisfy a long lifetime. Fix #52126
2 parents ffaf3d2 + f153be6 commit 67f9c71

5 files changed

+107
-12
lines changed

src/librustc_typeck/check/op.rs

+18-11
Original file line numberDiff line numberDiff line change
@@ -165,18 +165,25 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
165165
op,
166166
is_assign);
167167

168-
let lhs_needs = match is_assign {
169-
IsAssign::Yes => Needs::MutPlace,
170-
IsAssign::No => Needs::None
168+
let lhs_ty = match is_assign {
169+
IsAssign::No => {
170+
// Find a suitable supertype of the LHS expression's type, by coercing to
171+
// a type variable, to pass as the `Self` to the trait, avoiding invariant
172+
// trait matching creating lifetime constraints that are too strict.
173+
// E.g. adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result
174+
// in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`.
175+
let lhs_ty = self.check_expr_with_needs(lhs_expr, Needs::None);
176+
let fresh_var = self.next_ty_var(TypeVariableOrigin::MiscVariable(lhs_expr.span));
177+
self.demand_coerce(lhs_expr, lhs_ty, fresh_var, AllowTwoPhase::No)
178+
}
179+
IsAssign::Yes => {
180+
// rust-lang/rust#52126: We have to use strict
181+
// equivalence on the LHS of an assign-op like `+=`;
182+
// overwritten or mutably-borrowed places cannot be
183+
// coerced to a supertype.
184+
self.check_expr_with_needs(lhs_expr, Needs::MutPlace)
185+
}
171186
};
172-
// Find a suitable supertype of the LHS expression's type, by coercing to
173-
// a type variable, to pass as the `Self` to the trait, avoiding invariant
174-
// trait matching creating lifetime constraints that are too strict.
175-
// E.g. adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result
176-
// in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`.
177-
let lhs_ty = self.check_expr_with_needs(lhs_expr, lhs_needs);
178-
let fresh_var = self.next_ty_var(TypeVariableOrigin::MiscVariable(lhs_expr.span));
179-
let lhs_ty = self.demand_coerce(lhs_expr, lhs_ty, fresh_var, AllowTwoPhase::No);
180187
let lhs_ty = self.resolve_type_vars_with_obligations(lhs_ty);
181188

182189
// NB: As we have not yet type-checked the RHS, we don't have the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0597]: `line` does not live long enough
2+
--> $DIR/issue-52126-assign-op-invariance.rs:44:28
3+
|
4+
LL | let v: Vec<&str> = line.split_whitespace().collect();
5+
| ^^^^ borrowed value does not live long enough
6+
LL | //~^ ERROR `line` does not live long enough
7+
LL | println!("accumulator before add_assign {:?}", acc.map);
8+
| ------- borrow later used here
9+
...
10+
LL | }
11+
| - borrowed value only lives until here
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0597`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Issue 52126: With respect to variance, the assign-op's like += were
12+
// accidentally lumped together with other binary op's. In both cases
13+
// we were coercing the LHS of the op to the expected supertype.
14+
//
15+
// The problem is that since the LHS of += is modified, we need the
16+
// parameter to be invariant with respect to the overall type, not
17+
// covariant.
18+
19+
use std::collections::HashMap;
20+
use std::ops::AddAssign;
21+
22+
pub fn main() {
23+
panics();
24+
}
25+
26+
pub struct Counter<'l> {
27+
map: HashMap<&'l str, usize>,
28+
}
29+
30+
impl<'l> AddAssign for Counter<'l>
31+
{
32+
fn add_assign(&mut self, rhs: Counter<'l>) {
33+
rhs.map.into_iter().for_each(|(key, val)| {
34+
let count = self.map.entry(key).or_insert(0);
35+
*count += val;
36+
});
37+
}
38+
}
39+
40+
/// often times crashes, if not prints invalid strings
41+
pub fn panics() {
42+
let mut acc = Counter{map: HashMap::new()};
43+
for line in vec!["123456789".to_string(), "12345678".to_string()] {
44+
let v: Vec<&str> = line.split_whitespace().collect();
45+
//~^ ERROR `line` does not live long enough
46+
println!("accumulator before add_assign {:?}", acc.map);
47+
let mut map = HashMap::new();
48+
for str_ref in v {
49+
let e = map.entry(str_ref);
50+
println!("entry: {:?}", e);
51+
let count = e.or_insert(0);
52+
*count += 1;
53+
}
54+
let cnt2 = Counter{map};
55+
acc += cnt2;
56+
println!("accumulator after add_assign {:?}", acc.map);
57+
// line gets dropped here but references are kept in acc.map
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0597]: `line` does not live long enough
2+
--> $DIR/issue-52126-assign-op-invariance.rs:44:28
3+
|
4+
LL | let v: Vec<&str> = line.split_whitespace().collect();
5+
| ^^^^ borrowed value does not live long enough
6+
...
7+
LL | }
8+
| - `line` dropped here while still borrowed
9+
LL | }
10+
| - borrowed value needs to live until here
11+
12+
error: aborting due to previous error
13+
14+
For more information about this error, try `rustc --explain E0597`.

src/test/ui/nll/constant.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// Test that MIR borrowck and NLL analysis can handle constants of
1212
// arbitrary types without ICEs.
1313

14-
// compile-flags:-Zborrowck=mir -Zverbose
14+
// compile-flags:-Zborrowck=mir
1515
// compile-pass
1616

1717
const HI: &str = "hi";

0 commit comments

Comments
 (0)