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
}

0 commit comments

Comments
 (0)