Skip to content

Commit 2f0c821

Browse files
blussoli-obk
authored andcommitted
Change untagged_unions to not allow union fields with drop
Union fields may now never have a type with attached destructor. This for example allows unions to use arbitrary field types only by wrapping them in ManuallyDrop. The stable rule remains, that union fields must be Copy. We use the new rule for the `untagged_union` feature. See RFC 2514. Note for ui tests: We can't test move out through Box's deref-move since we can't have a Box in a union anymore.
1 parent 84ca0a1 commit 2f0c821

16 files changed

+222
-76
lines changed

src/librustc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ parking_lot = "0.9"
3535
byteorder = { version = "1.3" }
3636
chalk-engine = { version = "0.9.0", default-features=false }
3737
rustc_fs_util = { path = "../librustc_fs_util" }
38-
smallvec = { version = "0.6.7", features = ["union", "may_dangle"] }
38+
smallvec = { version = "0.6.8", features = ["union", "may_dangle"] }
3939
measureme = "0.3"

src/librustc/ty/util.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,8 @@ impl<'tcx> ty::TyS<'tcx> {
805805
///
806806
/// (Note that this implies that if `ty` has a destructor attached,
807807
/// then `needs_drop` will definitely return `true` for `ty`.)
808+
///
809+
/// Note that this method is used to check eligible types in unions.
808810
#[inline]
809811
pub fn needs_drop(&'tcx self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> bool {
810812
tcx.needs_drop_raw(param_env.and(self)).0

src/librustc_typeck/check/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,9 +1387,38 @@ fn check_union(tcx: TyCtxt<'_>, id: hir::HirId, span: Span) {
13871387
def.destructor(tcx); // force the destructor to be evaluated
13881388
check_representable(tcx, span, def_id);
13891389
check_transparent(tcx, span, def_id);
1390+
check_union_fields(tcx, span, def_id);
13901391
check_packed(tcx, span, def_id);
13911392
}
13921393

1394+
fn check_union_fields<'tcx>(tcx: TyCtxt<'tcx>, _sp: Span, item_def_id: DefId) -> bool {
1395+
// Without the feature we check Copy types only later
1396+
if !tcx.features().untagged_unions {
1397+
return true;
1398+
}
1399+
let t = tcx.type_of(item_def_id);
1400+
if let ty::Adt(def, substs) = t.sty {
1401+
if def.is_union() {
1402+
let fields = &def.non_enum_variant().fields;
1403+
for field in fields {
1404+
let field_ty = field.ty(tcx, substs);
1405+
// We are currently checking the type this field came from, so it must be local
1406+
let field_span = tcx.hir().span_if_local(field.did).unwrap();
1407+
let param_env = tcx.param_env(field.did);
1408+
if field_ty.needs_drop(tcx, param_env) {
1409+
struct_span_err!(tcx.sess, field_span, E0740,
1410+
"unions may not contain fields that need dropping")
1411+
.span_note(field_span,
1412+
"`std::mem::ManuallyDrop` can be used to wrap the type")
1413+
.emit();
1414+
return false;
1415+
}
1416+
}
1417+
}
1418+
}
1419+
return true;
1420+
}
1421+
13931422
/// Checks that an opaque type does not contain cycles and does not use `Self` or `T::Foo`
13941423
/// projections that would result in "inheriting lifetimes".
13951424
fn check_opaque<'tcx>(

src/librustc_typeck/error_codes.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4863,6 +4863,10 @@ assert_eq!(1, discriminant(&Enum::Struct{a: 7, b: 11}));
48634863
```
48644864
"##,
48654865

4866+
E0740: r##"
4867+
A `union` can not have fields with destructors.
4868+
"##,
4869+
48664870
E0733: r##"
48674871
Recursion in an `async fn` requires boxing. For example, this will not compile:
48684872
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#![feature(untagged_unions)]
2+
#![allow(dead_code)]
3+
// run-pass
4+
5+
use std::mem::needs_drop;
6+
use std::mem::ManuallyDrop;
7+
8+
struct NeedDrop;
9+
10+
impl Drop for NeedDrop {
11+
fn drop(&mut self) {}
12+
}
13+
14+
union UnionOk1<T> {
15+
empty: (),
16+
value: ManuallyDrop<T>,
17+
}
18+
19+
union UnionOk2 {
20+
value: ManuallyDrop<NeedDrop>,
21+
}
22+
23+
#[allow(dead_code)]
24+
union UnionOk3<T: Copy> {
25+
empty: (),
26+
value: T,
27+
}
28+
29+
trait Foo { }
30+
31+
trait ImpliesCopy : Copy { }
32+
33+
#[allow(dead_code)]
34+
union UnionOk4<T: ImpliesCopy> {
35+
value: T,
36+
}
37+
38+
fn main() {
39+
// NeedDrop should not make needs_drop true
40+
assert!(!needs_drop::<UnionOk1<NeedDrop>>());
41+
assert!(!needs_drop::<UnionOk3<&dyn Foo>>());
42+
}

src/test/ui/feature-gates/feature-gate-associated_type_bounds.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![feature(untagged_unions)]
22

3-
trait Tr1 { type As1; }
4-
trait Tr2 { type As2; }
3+
trait Tr1 { type As1: Copy; }
4+
trait Tr2 { type As2: Copy; }
55

66
struct S1;
77
#[derive(Copy, Clone)]
@@ -32,7 +32,7 @@ enum _En1<T: Tr1<As1: Tr2>> {
3232

3333
union _Un1<T: Tr1<As1: Tr2>> {
3434
//~^ ERROR associated type bounds are unstable
35-
outest: T,
35+
outest: std::mem::ManuallyDrop<T>,
3636
outer: T::As1,
3737
inner: <T::As1 as Tr2>::As2,
3838
}

src/test/ui/rfc-2093-infer-outlives/explicit-union.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
error: rustc_outlives
2-
--> $DIR/explicit-union.rs:6:1
2+
--> $DIR/explicit-union.rs:5:1
33
|
4-
LL | / union Foo<'b, U> {
4+
LL | / union Foo<'b, U: Copy> {
55
LL | | bar: Bar<'b, U>
66
LL | | }
77
| |_^

src/test/ui/rfc-2093-infer-outlives/nested-union.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
error: rustc_outlives
2-
--> $DIR/nested-union.rs:6:1
2+
--> $DIR/nested-union.rs:5:1
33
|
4-
LL | / union Foo<'a, T> {
4+
LL | / union Foo<'a, T: Copy> {
55
LL | | field1: Bar<'a, T>
66
LL | | }
77
| |_^

src/test/ui/union/union-borrow-move-parent-sibling.stderr

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0502]: cannot borrow `u` (via `u.y`) as immutable because it is also borrowed as mutable (via `u.x.0`)
2-
--> $DIR/union-borrow-move-parent-sibling.rs:54:13
2+
--> $DIR/union-borrow-move-parent-sibling.rs:53:13
33
|
44
LL | let a = &mut u.x.0;
55
| ---------- mutable borrow occurs here (via `u.x.0`)
@@ -11,7 +11,7 @@ LL | use_borrow(a);
1111
= note: `u.y` is a field of the union `U`, so it overlaps the field `u.x.0`
1212

1313
error[E0382]: use of moved value: `u`
14-
--> $DIR/union-borrow-move-parent-sibling.rs:61:13
14+
--> $DIR/union-borrow-move-parent-sibling.rs:60:13
1515
|
1616
LL | let u = U { x: ((MockVec::new(), MockVec::new()), MockVec::new()) };
1717
| - move occurs because `u` has type `U`, which does not implement the `Copy` trait
@@ -21,7 +21,7 @@ LL | let b = u.y;
2121
| ^^^ value used here after move
2222

2323
error[E0502]: cannot borrow `u` (via `u.y`) as immutable because it is also borrowed as mutable (via `u.x.0.0`)
24-
--> $DIR/union-borrow-move-parent-sibling.rs:67:13
24+
--> $DIR/union-borrow-move-parent-sibling.rs:66:13
2525
|
2626
LL | let a = &mut (u.x.0).0;
2727
| -------------- mutable borrow occurs here (via `u.x.0.0`)
@@ -33,7 +33,7 @@ LL | use_borrow(a);
3333
= note: `u.y` is a field of the union `U`, so it overlaps the field `u.x.0.0`
3434

3535
error[E0382]: use of moved value: `u`
36-
--> $DIR/union-borrow-move-parent-sibling.rs:74:13
36+
--> $DIR/union-borrow-move-parent-sibling.rs:73:13
3737
|
3838
LL | let u = U { x: ((MockVec::new(), MockVec::new()), MockVec::new()) };
3939
| - move occurs because `u` has type `U`, which does not implement the `Copy` trait
@@ -43,7 +43,7 @@ LL | let b = u.y;
4343
| ^^^ value used here after move
4444

4545
error[E0502]: cannot borrow `u` (via `u.x`) as immutable because it is also borrowed as mutable (via `u.y`)
46-
--> $DIR/union-borrow-move-parent-sibling.rs:80:13
46+
--> $DIR/union-borrow-move-parent-sibling.rs:79:13
4747
|
4848
LL | let a = &mut *u.y;
4949
| --- mutable borrow occurs here (via `u.y`)

src/test/ui/union/union-derive-clone.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![feature(untagged_unions)]
22

3+
use std::mem::ManuallyDrop;
4+
35
#[derive(Clone)] //~ ERROR the trait bound `U1: std::marker::Copy` is not satisfied
46
union U1 {
57
a: u8,
@@ -18,14 +20,19 @@ union U3 {
1820
}
1921

2022
#[derive(Clone, Copy)]
21-
union U4<T> {
23+
union U4<T: Copy> {
2224
a: T, // OK
2325
}
2426

27+
#[derive(Clone, Copy)]
28+
union U5<T> {
29+
a: ManuallyDrop<T>, // OK
30+
}
31+
2532
#[derive(Clone)]
2633
struct CloneNoCopy;
2734

2835
fn main() {
29-
let u = U4 { a: CloneNoCopy };
30-
let w = u.clone(); //~ ERROR no method named `clone` found for type `U4<CloneNoCopy>`
36+
let u = U5 { a: ManuallyDrop::new(CloneNoCopy) };
37+
let w = u.clone(); //~ ERROR no method named `clone` found for type `U5<CloneNoCopy>`
3138
}

src/test/ui/union/union-derive-clone.stderr

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
error[E0277]: the trait bound `U1: std::marker::Copy` is not satisfied
2-
--> $DIR/union-derive-clone.rs:3:10
2+
--> $DIR/union-derive-clone.rs:5:10
33
|
44
LL | #[derive(Clone)]
55
| ^^^^^ the trait `std::marker::Copy` is not implemented for `U1`
66
|
77
= note: required by `std::clone::AssertParamIsCopy`
88

9-
error[E0599]: no method named `clone` found for type `U4<CloneNoCopy>` in the current scope
10-
--> $DIR/union-derive-clone.rs:30:15
9+
error[E0599]: no method named `clone` found for type `U5<CloneNoCopy>` in the current scope
10+
--> $DIR/union-derive-clone.rs:37:15
1111
|
12-
LL | union U4<T> {
12+
LL | union U5<T> {
1313
| ----------- method `clone` not found for this
1414
...
1515
LL | let w = u.clone();
1616
| ^^^^^ method not found in `U4<CloneNoCopy>`
1717
|
1818
= note: the method `clone` exists but the following trait bounds were not satisfied:
19-
`U4<CloneNoCopy> : std::clone::Clone`
19+
`U5<CloneNoCopy> : std::clone::Clone`
2020
= help: items from traits can only be used if the trait is implemented and in scope
2121
= note: the following trait defines an item `clone`, perhaps you need to implement it:
2222
candidate #1: `std::clone::Clone`

src/test/ui/union/union-unsafe.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
11
#![feature(untagged_unions)]
2+
use std::mem::ManuallyDrop;
23

34
union U1 {
45
a: u8
56
}
67

78
union U2 {
8-
a: String
9+
a: ManuallyDrop<String>
910
}
1011

1112
union U3<T> {
12-
a: T
13+
a: ManuallyDrop<T>
1314
}
1415

1516
union U4<T: Copy> {
1617
a: T
1718
}
1819

1920
fn generic_noncopy<T: Default>() {
20-
let mut u3 = U3 { a: T::default() };
21-
u3.a = T::default(); //~ ERROR assignment to non-`Copy` union field is unsafe
21+
let mut u3 = U3 { a: ManuallyDrop::new(T::default()) };
22+
u3.a = ManuallyDrop::new(T::default()); //~ ERROR assignment to non-`Copy` union field is unsafe
23+
*u3.a = T::default(); //~ ERROR access to union field is unsafe
2224
}
2325

2426
fn generic_copy<T: Copy + Default>() {
25-
let mut u3 = U3 { a: T::default() };
26-
u3.a = T::default(); // OK
27+
let mut u3 = U3 { a: ManuallyDrop::new(T::default()) };
28+
u3.a = ManuallyDrop::new(T::default()); // OK
29+
*u3.a = T::default(); //~ ERROR access to union field is unsafe
30+
2731
let mut u4 = U4 { a: T::default() };
2832
u4.a = T::default(); // OK
2933
}
@@ -32,14 +36,20 @@ fn main() {
3236
let mut u1 = U1 { a: 10 }; // OK
3337
let a = u1.a; //~ ERROR access to union field is unsafe
3438
u1.a = 11; // OK
39+
3540
let U1 { a } = u1; //~ ERROR access to union field is unsafe
3641
if let U1 { a: 12 } = u1 {} //~ ERROR access to union field is unsafe
3742
// let U1 { .. } = u1; // OK
3843

39-
let mut u2 = U2 { a: String::from("old") }; // OK
40-
u2.a = String::from("new"); //~ ERROR assignment to non-`Copy` union field is unsafe
41-
let mut u3 = U3 { a: 0 }; // OK
42-
u3.a = 1; // OK
43-
let mut u3 = U3 { a: String::from("old") }; // OK
44-
u3.a = String::from("new"); //~ ERROR assignment to non-`Copy` union field is unsafe
44+
let mut u2 = U2 { a: ManuallyDrop::new(String::from("old")) }; // OK
45+
u2.a = ManuallyDrop::new(String::from("new")); //~ ERROR assignment to non-`Copy` union
46+
*u2.a = String::from("new"); //~ ERROR access to union field is unsafe
47+
48+
let mut u3 = U3 { a: ManuallyDrop::new(0) }; // OK
49+
u3.a = ManuallyDrop::new(1); // OK
50+
*u3.a = 1; //~ ERROR access to union field is unsafe
51+
52+
let mut u3 = U3 { a: ManuallyDrop::new(String::from("old")) }; // OK
53+
u3.a = ManuallyDrop::new(String::from("new")); //~ ERROR assignment to non-`Copy` union
54+
*u3.a = String::from("new"); //~ ERROR access to union field is unsafe
4555
}

0 commit comments

Comments
 (0)