Skip to content

Commit

Permalink
coercion: use a generalization of the target to specialize the source…
Browse files Browse the repository at this point in the history
… to coerce instead of propagating the wrong type.
  • Loading branch information
eddyb committed Jul 26, 2015
1 parent a4c4cd8 commit 7e888f0
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 23 deletions.
143 changes: 120 additions & 23 deletions src/librustc_typeck/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@
//! sort of a minor point so I've opted to leave it for later---after all
//! we may want to adjust precisely when coercions occur.
use check::{autoderef, FnCtxt, LvaluePreference, UnresolvedTypeAction};
use check::{autoderef, impl_self_ty, FnCtxt, LvaluePreference, UnresolvedTypeAction};

use middle::infer::{self, Coercion};
use middle::subst::Substs;
use middle::traits::{self, ObligationCause};
use middle::traits::{predicate_for_trait_def, report_selection_error};
use middle::ty::{AutoDerefRef, AdjustDerefRef};
Expand All @@ -79,6 +80,24 @@ struct Coerce<'a, 'tcx: 'a> {

type CoerceResult<'tcx> = RelateResult<'tcx, Option<ty::AutoAdjustment<'tcx>>>;

/// The result of an attempt at coercing a Source type to a
/// Target type via unsizing (`Source: CoerceUnsized<Target>`).
/// If successful, all `CoerceUnsized` and `Unsized` obligations were selected.
/// Other obligations, such as `T: Trait` for `&T -> &Trait`, are provided
/// alongside the adjustment, to be enforced later.
type CoerceUnsizedResult<'tcx> = Result<(AutoDerefRef<'tcx>,
Vec<traits::PredicateObligation<'tcx>>),
CoerceUnsizedError>;

#[derive(PartialEq, Eq)]
enum CoerceUnsizedError {
/// Source definitely does not implement `CoerceUnsized<Target>`.
Unapplicable,

/// Source might implement `CoerceUnsized<Target>`.
Ambiguous,
}

impl<'f, 'tcx> Coerce<'f, 'tcx> {
fn tcx(&self) -> &ty::ctxt<'tcx> {
self.fcx.tcx()
Expand All @@ -99,8 +118,11 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {

// Consider coercing the subtype to a DST
let unsize = self.fcx.infcx().commit_if_ok(|_| self.coerce_unsized(a, b));
if unsize.is_ok() {
return unsize;
if let Ok((adjustment, leftover_predicates)) = unsize {
for obligation in leftover_predicates {
self.fcx.register_predicate(obligation);
}
return Ok(Some(AdjustDerefRef(adjustment)));
}

// Examine the supertype and consider auto-borrowing.
Expand All @@ -119,23 +141,97 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
_ => {}
}

let b = self.fcx.infcx().shallow_resolve(b);
match a.sty {
ty::TyBareFn(Some(_), a_f) => {
// Function items are coercible to any closure
// type; function pointers are not (that would
// require double indirection).
self.coerce_from_fn_item(a, a_f, b)
return self.coerce_from_fn_item(a, a_f, b);
}
ty::TyBareFn(None, a_f) => {
// We permit coercion of fn pointers to drop the
// unsafe qualifier.
self.coerce_from_fn_pointer(a, a_f, b)
return self.coerce_from_fn_pointer(a, a_f, b);
}
_ => {
// Otherwise, just use subtyping rules.
self.subtype(a, b)
_ => {}
}

// Attempt to generalize the expected type in hopes of an unsizing
// coercion, where an intermediary stop-gap is usually necessary.
// This is the case with trait method calls where the returned type
// was not inferred, e.g. `Make::make(x: T): Box<Trait>`, if `Make`
// has many implementations. Unsizing coercion will be ambiguous
// and subtyping would result in a selection failure, if `Box<Trait>`
// does not implement `Make`, but `Box<T>` does. The stop-gap fix
// is `Make::make(x: T): Box<T>: Box<Trait>`.
// In that same case, the following generalization will attempt to
// apply `Box<_>` to the otherwise unconstrained `Make::make` return
// type and trigger selection, hoping to get the unambiguous source
// type `Box<T>` for the coercion to `Box<Trait>`.
// Subtyping rules are used if the generalization and second attempt
// at coercions through unsizing do not apply.

// The first unsizing coercion must have failed due to ambiguity.
if unsize.err() != Some(CoerceUnsizedError::Ambiguous) {
return self.subtype(a, b);
}

// The target type needs to be a structure or Box<T>.
// The only other types that implement CoerceUnsized are
// references and pointers and those have multiple forms,
// such as `*mut T -> *const Trait`.
match b.sty {
ty::TyBox(_) | ty::TyStruct(..) => {}
_ => return self.subtype(a, b)
}

// Construct a `Target: CoerceUnsized<Target>` predicate.
let trait_predicate = ty::Binder(ty::TraitRef {
def_id: self.tcx().lang_items.coerce_unsized_trait().unwrap(),
substs: self.tcx().mk_substs(Substs::new_trait(vec![b], vec![], b))
}).to_poly_trait_predicate();

// Select `Target: CoerceUnsized<Target>`.
let mut selcx = traits::SelectionContext::new(self.fcx.infcx());
let cause = ObligationCause::misc(self.origin.span(), self.fcx.body_id);
let obligation = traits::Obligation::new(cause, trait_predicate);
if let Ok(Some(traits::VtableImpl(i))) = selcx.select(&obligation) {
// There is a single applicable impl. If `Target = P<Trait>`, then
// the `Self` of this impl is some kind of supertype of `P<Trait>`,
// most likely `P<T> forall T: Unsize<U>`.
// This `Self` type, when all impl type parameters have been
// substituted with fresh inference variables (e.g. `P<_>`),
// will unify with the target type and all possible source types
// for a coercion.
// It can thus be used as a supertype of the source type,
// the generalized form that can allow fulfilling pending
// obligations and ultimately an unsizing coercion.
let success = self.fcx.infcx().commit_if_ok(|_| {
self.subtype(a, impl_self_ty(self.fcx,
self.origin.span(),
i.impl_def_id).ty)
});

if success.is_ok() {
// Select pending obligations to constrain the
// source type further, and resolve it again.
self.fcx.select_obligations_where_possible();
let a = self.fcx.infcx().shallow_resolve(a);

// Finally, attempt a coercion by unsizing again,
// now that the types are (hopefully) better known.
let unsize = self.fcx.infcx().commit_if_ok(|_| self.coerce_unsized(a, b));
if let Ok((adjustment, leftover_predicates)) = unsize {
for obligation in leftover_predicates {
self.fcx.register_predicate(obligation);
}
return Ok(Some(AdjustDerefRef(adjustment)));
}
}
}

self.subtype(a, b)
}

/// Reborrows `&mut A` to `&mut B` and `&(mut) A` to `&B`.
Expand Down Expand Up @@ -218,7 +314,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
fn coerce_unsized(&self,
source: Ty<'tcx>,
target: Ty<'tcx>)
-> CoerceResult<'tcx> {
-> CoerceUnsizedResult<'tcx> {
debug!("coerce_unsized(source={:?}, target={:?})",
source,
target);
Expand All @@ -229,7 +325,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
(u, cu)
} else {
debug!("Missing Unsize or CoerceUnsized traits");
return Err(TypeError::Mismatch);
return Err(CoerceUnsizedError::Unapplicable);
};

// Note, we want to avoid unnecessary unsizing. We don't want to coerce to
Expand All @@ -240,15 +336,19 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
// Handle reborrows before selecting `Source: CoerceUnsized<Target>`.
let (source, reborrow) = match (&source.sty, &target.sty) {
(&ty::TyRef(_, mt_a), &ty::TyRef(_, mt_b)) => {
try!(coerce_mutbls(mt_a.mutbl, mt_b.mutbl));
if coerce_mutbls(mt_a.mutbl, mt_b.mutbl).is_err() {
return Err(CoerceUnsizedError::Unapplicable);
}

let coercion = Coercion(self.origin.span());
let r_borrow = self.fcx.infcx().next_region_var(coercion);
let region = self.tcx().mk_region(r_borrow);
(mt_a.ty, Some(ty::AutoPtr(region, mt_b.mutbl)))
}
(&ty::TyRef(_, mt_a), &ty::TyRawPtr(mt_b)) => {
try!(coerce_mutbls(mt_a.mutbl, mt_b.mutbl));
if coerce_mutbls(mt_a.mutbl, mt_b.mutbl).is_err() {
return Err(CoerceUnsizedError::Unapplicable);
}
(mt_a.ty, Some(ty::AutoUnsafe(mt_b.mutbl)))
}
_ => (source, None)
Expand Down Expand Up @@ -287,9 +387,13 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
};
match selcx.select(&obligation.with(trait_ref)) {
// Uncertain or unimplemented.
Ok(None) | Err(traits::Unimplemented) => {
debug!("coerce_unsized: early return - can't prove obligation");
return Err(TypeError::Mismatch);
Ok(None) => {
debug!("coerce_unsized: early return (Ambiguous)");
return Err(CoerceUnsizedError::Ambiguous);
}
Err(traits::Unimplemented) => {
debug!("coerce_unsized: early return (Unapplicable)");
return Err(CoerceUnsizedError::Unapplicable);
}

// Object safety violations or miscellaneous.
Expand All @@ -308,18 +412,13 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
}
}

// Save all the obligations that are not for CoerceUnsized or Unsize.
for obligation in leftover_predicates {
self.fcx.register_predicate(obligation);
}

let adjustment = AutoDerefRef {
autoderefs: if reborrow.is_some() { 1 } else { 0 },
autoref: reborrow,
unsize: Some(target)
};
debug!("Success, coerced with {:?}", adjustment);
Ok(Some(AdjustDerefRef(adjustment)))
Ok((adjustment, leftover_predicates))
}

fn coerce_from_fn_pointer(&self,
Expand All @@ -333,7 +432,6 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
* into a closure or a `proc`.
*/

let b = self.fcx.infcx().shallow_resolve(b);
debug!("coerce_from_fn_pointer(a={:?}, b={:?})",
a, b);

Expand All @@ -360,7 +458,6 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
* into a closure or a `proc`.
*/

let b = self.fcx.infcx().shallow_resolve(b);
debug!("coerce_from_fn_item(a={:?}, b={:?})",
a, b);

Expand Down
134 changes: 134 additions & 0 deletions src/test/run-pass/coerce-box-protocol.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2015 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.

use std::rc::Rc;

mod protocol {
use std::ptr;

pub trait BoxPlace<T>: Place<T> {
fn make() -> Self;
}

pub trait Place<T> {
// NOTE(eddyb) The use of `&mut T` here is to force
// the LLVM `noalias` and `dereferenceable(sizeof(T))`
// attributes, which are required for eliding the copy
// and producing actual in-place initialization via RVO.
// Neither of those attributes are present on `*mut T`,
// but `&mut T` is not a great choice either, the proper
// way might be to add those attributes to `Unique<T>`.
unsafe fn pointer(&mut self) -> &mut T;
}

pub trait Boxed<T>: Sized {
type P: Place<T>;
fn write_and_fin(mut place: Self::P,
value: T)
-> Self {
unsafe {
ptr::write(place.pointer(), value);
Self::fin(place)
}
}
unsafe fn fin(filled: Self::P) -> Self;
}
}

macro_rules! box_ {
($x:expr) => {
::protocol::Boxed::write_and_fin(::protocol::BoxPlace::make(), $x)
}
}

// Hacky implementations of the box protocol for Box<T> and Rc<T>.
// They pass mem::uninitialized() to Box::new, and Rc::new, respectively,
// to allocate memory and will leak the allocation in case of unwinding.
mod boxed {
use std::mem;
use protocol;

pub struct Place<T> {
ptr: *mut T
}

impl<T> protocol::BoxPlace<T> for Place<T> {
fn make() -> Place<T> {
unsafe {
Place {
ptr: mem::transmute(Box::new(mem::uninitialized::<T>()))
}
}
}
}

impl<T> protocol::Place<T> for Place<T> {
unsafe fn pointer(&mut self) -> &mut T { &mut *self.ptr }
}

impl<T> protocol::Boxed<T> for Box<T> {
type P = Place<T>;
unsafe fn fin(place: Place<T>) -> Box<T> {
mem::transmute(place.ptr)
}
}
}

mod rc {
use std::mem;
use std::rc::Rc;
use protocol;

pub struct Place<T> {
rc_ptr: *mut (),
data_ptr: *mut T
}

impl<T> protocol::BoxPlace<T> for Place<T> {
fn make() -> Place<T> {
unsafe {
let rc = Rc::new(mem::uninitialized::<T>());
Place {
data_ptr: &*rc as *const _ as *mut _,
rc_ptr: mem::transmute(rc)
}
}
}
}

impl<T> protocol::Place<T> for Place<T> {
unsafe fn pointer(&mut self) -> &mut T {
&mut *self.data_ptr
}
}

impl<T> protocol::Boxed<T> for Rc<T> {
type P = Place<T>;
unsafe fn fin(place: Place<T>) -> Rc<T> {
mem::transmute(place.rc_ptr)
}
}
}

fn main() {
let v = vec![1, 2, 3];

let bx: Box<_> = box_!(|| &v);
let rc: Rc<_> = box_!(|| &v);

assert_eq!(bx(), &v);
assert_eq!(rc(), &v);

let bx_trait: Box<Fn() -> _> = box_!(|| &v);
let rc_trait: Rc<Fn() -> _> = box_!(|| &v);

assert_eq!(bx(), &v);
assert_eq!(rc(), &v);
}

0 comments on commit 7e888f0

Please sign in to comment.