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

Unsized tuple coercions #42527

Merged
merged 6 commits into from
Jun 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# `unsized_tuple_coercion`

The tracking issue for this feature is: [#42877]

[#42877]: https://github.com/rust-lang/rust/issues/42877

------------------------

This is a part of [RFC0401]. According to the RFC, there should be an implementation like this:

```rust
impl<..., T, U: ?Sized> Unsized<(..., U)> for (..., T) where T: Unsized<U> {}
```

This implementation is currently gated behind `#[feature(unsized_tuple_coercion)]` to avoid insta-stability. Therefore you can use it like this:

```rust
#![feature(unsized_tuple_coercion)]

fn main() {
let x : ([i32; 3], [i32; 3]) = ([1, 2, 3], [4, 5, 6]);
let y : &([i32; 3], [i32]) = &x;
assert_eq!(y.1[0], 4);
}
```

[RFC0401]: https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md
5 changes: 4 additions & 1 deletion src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1060,7 +1060,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
err.note("slice and array elements must have `Sized` type");
}
ObligationCauseCode::TupleElem => {
err.note("tuple elements must have `Sized` type");
err.note("only the last element of a tuple may have a dynamically sized type");
}
ObligationCauseCode::ProjectionWf(data) => {
err.note(&format!("required so that the projection `{}` is well-formed",
Expand Down Expand Up @@ -1097,6 +1097,9 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
ObligationCauseCode::AssignmentLhsSized => {
err.note("the left-hand-side of an assignment must have a statically known size");
}
ObligationCauseCode::TupleInitializerSized => {
err.note("tuples must have a statically known size to be initialized");
}
ObligationCauseCode::StructInitializerSized => {
err.note("structs must have a statically known size to be initialized");
}
Expand Down
2 changes: 2 additions & 0 deletions src/librustc/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ pub enum ObligationCauseCode<'tcx> {
// Various cases where expressions must be sized/copy/etc:
/// L = X implies that L is Sized
AssignmentLhsSized,
/// (x1, .., xn) must be Sized
TupleInitializerSized,
/// S { ... } must be Sized
StructInitializerSized,
/// Type of each variable must be Sized
Expand Down
40 changes: 38 additions & 2 deletions src/librustc/traits/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1651,6 +1651,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
def_id_a == def_id_b
}

// (.., T) -> (.., U).
(&ty::TyTuple(tys_a, _), &ty::TyTuple(tys_b, _)) => {
tys_a.len() == tys_b.len()
}

_ => false
};

Expand Down Expand Up @@ -2591,8 +2596,8 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
let inner_source = field.subst(tcx, substs_a);
let inner_target = field.subst(tcx, substs_b);

// Check that the source structure with the target's
// type parameters is a subtype of the target.
// Check that the source struct with the target's
// unsized parameters is equal to the target.
let params = substs_a.iter().enumerate().map(|(i, &k)| {
if ty_params.contains(i) {
Kind::from(substs_b.type_at(i))
Expand All @@ -2617,6 +2622,37 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
&[inner_target]));
}

// (.., T) -> (.., U).
(&ty::TyTuple(tys_a, _), &ty::TyTuple(tys_b, _)) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll prefer to merge this with the ADT code somehow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've thought before that we should have an "infinite family" of tuple ADTs. One complication is that ADTs have def-ids...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code above mk_adt largely depends on def-ids and type parameters, so I doubt I can merge this part with tuple's. The codes below mk_adt/mk_tup are similar, but I don't think its worth merging...

assert_eq!(tys_a.len(), tys_b.len());

// The last field of the tuple has to exist.
let (a_last, a_mid) = if let Some(x) = tys_a.split_last() {
x
} else {
return Err(Unimplemented);
};
let b_last = tys_b.last().unwrap();

// Check that the source tuple with the target's
// last element is equal to the target.
let new_tuple = tcx.mk_tup(a_mid.iter().chain(Some(b_last)), false);
let InferOk { obligations, .. } =
self.infcx.at(&obligation.cause, obligation.param_env)
.eq(target, new_tuple)
.map_err(|_| Unimplemented)?;
self.inferred_obligations.extend(obligations);

// Construct the nested T: Unsize<U> predicate.
nested.push(tcx.predicate_for_trait_def(
obligation.param_env,
obligation.cause.clone(),
obligation.predicate.def_id(),
obligation.recursion_depth + 1,
a_last,
&[b_last]));
}

_ => bug!()
};

Expand Down
3 changes: 3 additions & 0 deletions src/librustc/traits/structural_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ impl<'a, 'tcx> Lift<'tcx> for traits::ObligationCauseCode<'a> {
tcx.lift(&ty).map(super::ObjectCastObligation)
}
super::AssignmentLhsSized => Some(super::AssignmentLhsSized),
super::TupleInitializerSized => Some(super::TupleInitializerSized),
super::StructInitializerSized => Some(super::StructInitializerSized),
super::VariableType(id) => Some(super::VariableType(id)),
super::ReturnType(id) => Some(super::ReturnType(id)),
Expand Down Expand Up @@ -476,6 +477,7 @@ impl<'tcx> TypeFoldable<'tcx> for traits::ObligationCauseCode<'tcx> {
super::TupleElem |
super::ItemObligation(_) |
super::AssignmentLhsSized |
super::TupleInitializerSized |
super::StructInitializerSized |
super::VariableType(_) |
super::ReturnType(_) |
Expand Down Expand Up @@ -523,6 +525,7 @@ impl<'tcx> TypeFoldable<'tcx> for traits::ObligationCauseCode<'tcx> {
super::TupleElem |
super::ItemObligation(_) |
super::AssignmentLhsSized |
super::TupleInitializerSized |
super::StructInitializerSized |
super::VariableType(_) |
super::ReturnType(_) |
Expand Down
10 changes: 7 additions & 3 deletions src/librustc/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1220,12 +1220,16 @@ impl<'a, 'tcx> Layout {
}

ty::TyTuple(tys, _) => {
// FIXME(camlorn): if we ever allow unsized tuples, this needs to be checked.
// See the univariant case below to learn how.
let kind = if tys.len() == 0 {
StructKind::AlwaysSizedUnivariant
} else {
StructKind::MaybeUnsizedUnivariant
};

let st = Struct::new(dl,
&tys.iter().map(|ty| ty.layout(tcx, param_env))
.collect::<Result<Vec<_>, _>>()?,
&ReprOptions::default(), StructKind::AlwaysSizedUnivariant, ty)?;
&ReprOptions::default(), kind, ty)?;
Univariant { variant: st, non_zero: false }
}

Expand Down
29 changes: 20 additions & 9 deletions src/librustc/ty/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,26 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
target: Ty<'tcx>)
-> (Ty<'tcx>, Ty<'tcx>) {
let (mut a, mut b) = (source, target);
while let (&TyAdt(a_def, a_substs), &TyAdt(b_def, b_substs)) = (&a.sty, &b.sty) {
if a_def != b_def || !a_def.is_struct() {
break;
}
match a_def.struct_variant().fields.last() {
Some(f) => {
a = f.ty(self, a_substs);
b = f.ty(self, b_substs);
}
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 let Some(f) = a_def.struct_variant().fields.last() {
a = f.ty(self, a_substs);
b = f.ty(self, b_substs);
} else {
break;
}
},
(&TyTuple(a_tys, _), &TyTuple(b_tys, _))
if a_tys.len() == b_tys.len() => {
if let Some(a_last) = a_tys.last() {
a = a_last;
b = b_tys.last().unwrap();
} else {
break;
}
},
_ => break,
}
}
Expand Down
12 changes: 9 additions & 3 deletions src/librustc_trans/glue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub fn size_and_align_of_dst<'a, 'tcx>(bcx: &Builder<'a, 'tcx>, t: Ty<'tcx>, inf
}
assert!(!info.is_null());
match t.sty {
ty::TyAdt(def, substs) => {
ty::TyAdt(..) | ty::TyTuple(..) => {
let ccx = bcx.ccx;
// First get the size of all statically known fields.
// Don't use size_of because it also rounds up to alignment, which we
Expand All @@ -101,8 +101,14 @@ pub fn size_and_align_of_dst<'a, 'tcx>(bcx: &Builder<'a, 'tcx>, t: Ty<'tcx>, inf

// Recurse to get the size of the dynamically sized field (must be
// the last field).
let last_field = def.struct_variant().fields.last().unwrap();
let field_ty = monomorphize::field_ty(bcx.tcx(), substs, last_field);
let field_ty = match t.sty {
ty::TyAdt(def, substs) => {
let last_field = def.struct_variant().fields.last().unwrap();
monomorphize::field_ty(bcx.tcx(), substs, last_field)
},
ty::TyTuple(tys, _) => tys.last().unwrap(),
_ => unreachable!(),
};
let (unsized_size, unsized_align) = size_and_align_of_dst(bcx, field_ty, info);

// FIXME (#26403, #27023): We should be adding padding
Expand Down
21 changes: 20 additions & 1 deletion src/librustc_typeck/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ use rustc::ty::relate::RelateResult;
use rustc::ty::subst::Subst;
use errors::DiagnosticBuilder;
use syntax::abi;
use syntax::feature_gate;
use syntax::ptr::P;
use syntax_pos;

Expand Down Expand Up @@ -520,14 +521,24 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {
coerce_source,
&[coerce_target]));

let mut has_unsized_tuple_coercion = false;

// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
// inference might unify those two inner type variables later.
let traits = [coerce_unsized_did, unsize_did];
while let Some(obligation) = queue.pop_front() {
debug!("coerce_unsized resolve step: {:?}", obligation);
let trait_ref = match obligation.predicate {
ty::Predicate::Trait(ref tr) if traits.contains(&tr.def_id()) => tr.clone(),
ty::Predicate::Trait(ref tr) if traits.contains(&tr.def_id()) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is... ok.

if unsize_did == tr.def_id() {
if let ty::TyTuple(..) = tr.0.input_types().nth(1).unwrap().sty {
debug!("coerce_unsized: found unsized tuple coercion");
has_unsized_tuple_coercion = true;
}
}
tr.clone()
}
_ => {
coercion.obligations.push(obligation);
continue;
Expand Down Expand Up @@ -557,6 +568,14 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {
}
}

if has_unsized_tuple_coercion && !self.tcx.sess.features.borrow().unsized_tuple_coercion {
feature_gate::emit_feature_err(&self.tcx.sess.parse_sess,
"unsized_tuple_coercion",
self.cause.span,
feature_gate::GateIssue::Language,
feature_gate::EXPLAIN_UNSIZED_TUPLE_COERCION);
}

Ok(coercion)
}

Expand Down
1 change: 1 addition & 0 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3854,6 +3854,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
if tuple.references_error() {
tcx.types.err
} else {
self.require_type_is_sized(tuple, expr.span, traits::TupleInitializerSized);
tuple
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,9 @@ declare_features! (

// Allows a test to fail without failing the whole suite
(active, allow_fail, "1.19.0", Some(42219)),

// Allows unsized tuple coercion.
(active, unsized_tuple_coercion, "1.20.0", Some(42877)),
);

declare_features! (
Expand Down Expand Up @@ -1041,6 +1044,9 @@ pub const EXPLAIN_VIS_MATCHER: &'static str =
pub const EXPLAIN_PLACEMENT_IN: &'static str =
"placement-in expression syntax is experimental and subject to change.";

pub const EXPLAIN_UNSIZED_TUPLE_COERCION: &'static str =
"Unsized tuple coercion is not stable enough for use and is subject to change";

struct PostExpansionVisitor<'a> {
context: &'a Context<'a>,
}
Expand Down
50 changes: 50 additions & 0 deletions src/test/compile-fail/dst-bad-assign-3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2014 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.

// Forbid assignment into a dynamically sized type.

#![feature(unsized_tuple_coercion)]

type Fat<T: ?Sized> = (isize, &'static str, T);
//~^ WARNING trait bounds are not (yet) enforced

#[derive(PartialEq,Eq)]
struct Bar;

#[derive(PartialEq,Eq)]
struct Bar1 {
f: isize
}

trait ToBar {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these extra methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because it was copied from dst-bad-assign.rs.

fn to_bar(&self) -> Bar;
fn to_val(&self) -> isize;
}

impl ToBar for Bar1 {
fn to_bar(&self) -> Bar {
Bar
}
fn to_val(&self) -> isize {
self.f
}
}

pub fn main() {
// Assignment.
let f5: &mut Fat<ToBar> = &mut (5, "some str", Bar1 {f :42});
let z: Box<ToBar> = Box::new(Bar1 {f: 36});
f5.2 = Bar1 {f: 36};
//~^ ERROR mismatched types
//~| expected type `ToBar`
//~| found type `Bar1`
//~| expected trait ToBar, found struct `Bar1`
//~| ERROR `ToBar: std::marker::Sized` is not satisfied
}
14 changes: 14 additions & 0 deletions src/test/compile-fail/dst-bad-coerce1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

// Attempt to change the type as well as unsizing.

#![feature(unsized_tuple_coercion)]

struct Fat<T: ?Sized> {
ptr: T
}
Expand All @@ -29,4 +31,16 @@ pub fn main() {
let f2: &Fat<Foo> = &f1;
let f3: &Fat<Bar> = f2;
//~^ ERROR `Foo: Bar` is not satisfied

// Tuple with a vec of isize.
let f1 = ([1, 2, 3],);
let f2: &([isize; 3],) = &f1;
let f3: &([usize],) = f2;
//~^ ERROR mismatched types

// Tuple with a trait.
let f1 = (Foo,);
let f2: &(Foo,) = &f1;
let f3: &(Bar,) = f2;
//~^ ERROR `Foo: Bar` is not satisfied
}
10 changes: 10 additions & 0 deletions src/test/compile-fail/dst-bad-coerce2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,14 @@ pub fn main() {
let f1 = Fat { ptr: Foo };
let f2: &Fat<Foo> = &f1;
let f3: &mut Fat<Bar> = f2; //~ ERROR mismatched types

// Tuple with a vec of ints.
let f1 = ([1, 2, 3],);
let f2: &([isize; 3],) = &f1;
let f3: &mut ([isize],) = f2; //~ ERROR mismatched types

// Tuple with a trait.
let f1 = (Foo,);
let f2: &(Foo,) = &f1;
let f3: &mut (Bar,) = f2; //~ ERROR mismatched types
}
Loading