Skip to content

Implement unsized unions #47650

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

Closed
wants to merge 13 commits into from
6 changes: 5 additions & 1 deletion src/librustc/middle/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
}
}

// There's no good place to insert stability check for non-Copy unions,
// There's no good place to insert stability check for non-Copy or unsized unions,
// so semi-randomly perform it here in stability.rs
hir::ItemUnion(..) if !self.tcx.sess.features.borrow().untagged_unions => {
let def_id = self.tcx.hir.local_def_id(item.id);
Expand All @@ -692,6 +692,10 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
emit_feature_err(&self.tcx.sess.parse_sess,
"untagged_unions", item.span, GateIssue::Language,
"unions with non-`Copy` fields are unstable");
} else if !ty.is_sized(self.tcx, param_env, item.span) {
emit_feature_err(&self.tcx.sess.parse_sess,
"untagged_unions", item.span, GateIssue::Language,
"unsized unions are unstable");
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1342,7 +1342,8 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
sized type");
}
AdtKind::Union => {
err.note("no field of a union may have a dynamically sized type");
err.note("only single-field unions may have a dynamically \
sized type");
}
AdtKind::Enum => {
err.note("no field of an enum variant may have a dynamically sized type");
Expand Down
4 changes: 3 additions & 1 deletion src/librustc/traits/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1957,7 +1957,9 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
(&ty::TyArray(..), &ty::TySlice(_)) => true,

// Struct<T> -> Struct<U>.
(&ty::TyAdt(def_id_a, _), &ty::TyAdt(def_id_b, _)) if def_id_a.is_struct() => {
// Union<T> -> Union<U>.
(&ty::TyAdt(def_id_a, _), &ty::TyAdt(def_id_b, _))
if def_id_a.is_struct() || def_id_a.is_union() => {
def_id_a == def_id_b
}

Expand Down
22 changes: 19 additions & 3 deletions src/librustc/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,7 @@ fn layout_raw<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
tcx.layout_depth.set(depth+1);
let cx = LayoutCx { tcx, param_env };
let layout = cx.layout_raw_uncached(ty);
debug!("computed layout, ty = {:?}, layout = {:?}", ty, layout);
tcx.layout_depth.set(depth);

layout
Expand Down Expand Up @@ -1208,6 +1209,7 @@ impl<'a, 'tcx> LayoutCx<'tcx, TyCtxt<'a, 'tcx, 'tcx>> {
}

let unsized_part = tcx.struct_tail(pointee);
debug!("compute_uncached: unsized_part = {:?}", unsized_part.sty);
let metadata = match unsized_part.sty {
ty::TyForeign(..) => {
return Ok(tcx.intern_layout(LayoutDetails::scalar(self, data_ptr)));
Expand All @@ -1220,7 +1222,11 @@ impl<'a, 'tcx> LayoutCx<'tcx, TyCtxt<'a, 'tcx, 'tcx>> {
vtable.valid_range.start = 1;
vtable
}
_ => return Err(LayoutError::Unknown(unsized_part))
_ => {
debug!("compute_uncached: unsized type with unknown \
pointer layout: {:?}", unsized_part.sty);
return Err(LayoutError::Unknown(ty))
}
};

// Effectively a (ptr, meta) tuple.
Expand Down Expand Up @@ -1361,6 +1367,7 @@ impl<'a, 'tcx> LayoutCx<'tcx, TyCtxt<'a, 'tcx, 'tcx>> {
}).collect::<Result<Vec<_>, _>>()?;

if def.is_union() {
debug!("compute_uncached: calculating layout for union: {:?}", ty.sty);
let packed = def.repr.packed();
if packed && def.repr.align > 0 {
bug!("Union cannot be packed and aligned");
Expand All @@ -1379,8 +1386,17 @@ impl<'a, 'tcx> LayoutCx<'tcx, TyCtxt<'a, 'tcx, 'tcx>> {
}

let mut size = Size::from_bytes(0);
let mut sized = true;
for field in &variants[0] {
assert!(!field.is_unsized());
if field.is_unsized() {
if variants[0].len() > 1 {
bug!("compute_uncached: field {:?} of union {:?} \
is unsized, but is not the only field",
field, ty);
} else {
sized = false;
}
}

if !packed {
align = align.max(field.align);
Expand All @@ -1391,7 +1407,7 @@ impl<'a, 'tcx> LayoutCx<'tcx, TyCtxt<'a, 'tcx, 'tcx>> {
return Ok(tcx.intern_layout(LayoutDetails {
variants: Variants::Single { index: 0 },
fields: FieldPlacement::Union(variants[0].len()),
abi: Abi::Aggregate { sized: true },
abi: Abi::Aggregate { sized },
align,
size: size.abi_align(align)
}));
Expand Down
6 changes: 4 additions & 2 deletions src/librustc/ty/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
loop {
match ty.sty {
ty::TyAdt(def, substs) => {
if !def.is_struct() {
if !def.is_struct() && !def.is_union() {
break;
}
match def.non_enum_variant().fields.last() {
Expand Down Expand Up @@ -328,7 +328,9 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
loop {
match (&a.sty, &b.sty) {
(&TyAdt(a_def, a_substs), &TyAdt(b_def, b_substs))
if a_def == b_def && a_def.is_struct() => {
if a_def == b_def && (
a_def.is_struct() || a_def.is_union()
) => {
if let Some(f) = a_def.non_enum_variant().fields.last() {
a = f.ty(self, a_substs);
b = f.ty(self, b_substs);
Expand Down
45 changes: 33 additions & 12 deletions src/librustc_typeck/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,21 @@ impl<'a, 'gcx> CheckTypeWellFormedVisitor<'a, 'gcx> {
self.check_item_type(item);
}
hir::ItemStruct(ref struct_def, ref ast_generics) => {
self.check_type_defn(item, false, |fcx| {
self.check_type_defn(item, |fcx| {
vec![fcx.non_enum_variant(struct_def)]
});

self.check_variances_for_type_defn(item, ast_generics);
}
hir::ItemUnion(ref struct_def, ref ast_generics) => {
self.check_type_defn(item, true, |fcx| {
self.check_type_defn(item, |fcx| {
vec![fcx.non_enum_variant(struct_def)]
});

self.check_variances_for_type_defn(item, ast_generics);
}
hir::ItemEnum(ref enum_def, ref ast_generics) => {
self.check_type_defn(item, true, |fcx| {
self.check_type_defn(item, |fcx| {
fcx.enum_variants(enum_def)
});

Expand Down Expand Up @@ -221,7 +221,7 @@ impl<'a, 'gcx> CheckTypeWellFormedVisitor<'a, 'gcx> {
}

/// In a type definition, we check that to ensure that the types of the fields are well-formed.
fn check_type_defn<F>(&mut self, item: &hir::Item, all_sized: bool, mut lookup_fields: F)
fn check_type_defn<F>(&mut self, item: &hir::Item, mut lookup_fields: F)
where F: for<'fcx, 'tcx> FnMut(&FnCtxt<'fcx, 'gcx, 'tcx>) -> Vec<AdtVariant<'tcx>>
{
self.for_item(item).with_fcx(|fcx, this| {
Expand All @@ -242,16 +242,37 @@ impl<'a, 'gcx> CheckTypeWellFormedVisitor<'a, 'gcx> {
ty.needs_drop(this.tcx, this.tcx.param_env(def_id))
}
};
let unsized_len = if
all_sized ||
variant.fields.is_empty() ||
needs_drop_copy()
{
0

let fields_that_must_be_sized = if needs_drop_copy() {
// all fields must be sized
// see definition of needs_drop_copy above^
&variant.fields[..]
} else {
1
match item.node {
hir::ItemStruct(..) => {
// last field in a struct may be unsized
&variant.fields[..variant.fields.len().max(1)-1]
}
hir::ItemUnion(..) => {
if variant.fields.len() == 1 {
// if there is only one field, it may be unsized
&[]
} else {
// otherwise all fields must be sized
&variant.fields[..]
}
}
hir::ItemEnum(..) => {
// all fields in an enum variant must be sized
&variant.fields[..]
}

_ => span_bug!(item.span,
"unexpected item {:?}", item.node)
}
};
for field in &variant.fields[..variant.fields.len() - unsized_len] {

for field in fields_that_must_be_sized {
fcx.register_bound(
field.ty,
fcx.tcx.require_lang_item(lang_items::SizedTraitLangItem),
Expand Down
2 changes: 1 addition & 1 deletion src/test/compile-fail/union/union-unsized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ union U {

union W {
a: u8,
b: str, //~ ERROR the trait bound `str: std::marker::Sized` is not satisfied
b: str,
}

fn main() {}
64 changes: 64 additions & 0 deletions src/test/run-pass/union/union-unsized.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2016 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.

#![feature(untagged_unions)]
#![feature(nll)]

use std::ptr;

trait Trait {}
impl<T> Trait for T {}

#[allow(unions_with_drop_fields)]
union NoDrop<T: ?Sized> {
value: T,
}

struct ShouldDrop<'a> {
dropped: &'a mut bool
}

impl<'a> Drop for ShouldDrop<'a> {
fn drop(&mut self) {
*self.dropped = true;
}
}

struct ShouldntDrop;

impl Drop for ShouldntDrop {
fn drop(&mut self) {
unsafe {
panic!("This should not be dropped!");
}
}
}

fn main() {
let mut dropped = false;
{
let mut should_drop = &mut NoDrop {
value: ShouldDrop {
dropped: &mut dropped
}
} as &mut NoDrop<Trait>;

unsafe {
ptr::drop_in_place(&mut should_drop.value);
}
}

assert!(dropped);

// NoDrop will be dropped, but the ShouldntDrop won't be
Box::new(NoDrop { value: ShouldntDrop }) as Box<NoDrop<Trait>>;

// FIXME: do something with Bar
}
11 changes: 11 additions & 0 deletions src/test/ui/feature-gate-untagged_unions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,15 @@ impl Drop for U5 {
fn drop(&mut self) {}
}

// the non-`Copy` error message has priority
// replace this with an "unsized unions are unstable" error message
// if non-`Copy` unions are stabilized before unsized unions
union U6<T: ?Sized> { //~ ERROR unions with non-`Copy` fields are unstable
a: T
}

union U7<T: Copy + ?Sized> { //~ ERROR unsized unions are unstable
a: T
}

fn main() {}
12 changes: 11 additions & 1 deletion src/test/ui/feature-gate-untagged_unions.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ LL | | }
|
= help: add #![feature(untagged_unions)] to the crate attributes to enable

error: aborting due to 3 previous errors
error[E0658]: unions with non-`Copy` fields are unstable (see issue #32836)
--> $DIR/feature-gate-untagged_unions.rs:38:1
|
LL | / union U6<T: ?Sized> { //~ ERROR unions with non-`Copy` fields are unstable
LL | | a: T
LL | | }
| |_^
|
= help: add #![feature(untagged_unions)] to the crate attributes to enable

error: aborting due to 4 previous errors

If you want more information on this error, try using "rustc --explain E0658"
1 change: 1 addition & 0 deletions src/test/ui/union/union-sized-field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#![feature(untagged_unions)]

union Foo<T: ?Sized> {
t: u32,
value: T, //~ ERROR the trait bound `T: std::marker::Sized` is not satisfied
}

Expand Down
8 changes: 4 additions & 4 deletions src/test/ui/union/union-sized-field.stderr
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
error[E0277]: the trait bound `T: std::marker::Sized` is not satisfied
--> $DIR/union-sized-field.rs:14:5
--> $DIR/union-sized-field.rs:15:5
|
LL | value: T, //~ ERROR the trait bound `T: std::marker::Sized` is not satisfied
| ^^^^^^^^ `T` does not have a constant size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `T`
= help: consider adding a `where T: std::marker::Sized` bound
= note: no field of a union may have a dynamically sized type
= note: only single-field unions may have a dynamically sized type

error[E0277]: the trait bound `T: std::marker::Sized` is not satisfied
--> $DIR/union-sized-field.rs:18:5
--> $DIR/union-sized-field.rs:19:5
|
LL | value: T, //~ ERROR the trait bound `T: std::marker::Sized` is not satisfied
| ^^^^^^^^ `T` does not have a constant size known at compile-time
Expand All @@ -19,7 +19,7 @@ LL | value: T, //~ ERROR the trait bound `T: std::marker::Sized` is not sati
= note: only the last field of a struct may have a dynamically sized type

error[E0277]: the trait bound `T: std::marker::Sized` is not satisfied
--> $DIR/union-sized-field.rs:23:11
--> $DIR/union-sized-field.rs:24:11
|
LL | Value(T), //~ ERROR the trait bound `T: std::marker::Sized` is not satisfied
| ^^ `T` does not have a constant size known at compile-time
Expand Down