Skip to content

Commit

Permalink
rustc: compute the vtable base of a supertrait during selection. Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyb committed Jul 4, 2015
1 parent 96d24a5 commit 536e71b
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 101 deletions.
8 changes: 5 additions & 3 deletions src/librustc/middle/traits/mod.rs
Expand Up @@ -291,11 +291,13 @@ pub struct VtableBuiltinData<N> {
/// for the object type `Foo`.
#[derive(PartialEq,Eq,Clone)]
pub struct VtableObjectData<'tcx> {
/// the object type `Foo`.
pub object_ty: Ty<'tcx>,

/// `Foo` upcast to the obligation trait. This will be some supertrait of `Foo`.
pub upcast_trait_ref: ty::PolyTraitRef<'tcx>,

/// The vtable is formed by concatenating together the method lists of
/// the base object trait and all supertraits; this is the start of
/// `upcast_trait_ref`'s methods in that vtable.
pub vtable_base: usize
}

/// Creates predicate obligations from the generic bounds.
Expand Down
10 changes: 5 additions & 5 deletions src/librustc/middle/traits/project.rs
Expand Up @@ -629,9 +629,10 @@ fn assemble_candidates_from_object_type<'cx,'tcx>(
selcx: &mut SelectionContext<'cx,'tcx>,
obligation: &ProjectionTyObligation<'tcx>,
obligation_trait_ref: &ty::TraitRef<'tcx>,
candidate_set: &mut ProjectionTyCandidateSet<'tcx>,
object_ty: Ty<'tcx>)
candidate_set: &mut ProjectionTyCandidateSet<'tcx>)
{
let self_ty = obligation_trait_ref.self_ty();
let object_ty = selcx.infcx().shallow_resolve(self_ty);
debug!("assemble_candidates_from_object_type(object_ty={:?})",
object_ty);
let data = match object_ty.sty {
Expand Down Expand Up @@ -684,10 +685,9 @@ fn assemble_candidates_from_impls<'cx,'tcx>(
candidate_set.vec.push(
ProjectionTyCandidate::Impl(data));
}
super::VtableObject(data) => {
super::VtableObject(_) => {
assemble_candidates_from_object_type(
selcx, obligation, obligation_trait_ref, candidate_set,
data.object_ty);
selcx, obligation, obligation_trait_ref, candidate_set);
}
super::VtableClosure(data) => {
candidate_set.vec.push(
Expand Down
73 changes: 32 additions & 41 deletions src/librustc/middle/traits/select.rs
Expand Up @@ -1362,12 +1362,21 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
debug!("assemble_candidates_from_object_ty: poly_trait_ref={:?}",
poly_trait_ref);

// see whether the object trait can be upcast to the trait we are looking for
let upcast_trait_refs = self.upcast(poly_trait_ref, obligation);
if upcast_trait_refs.len() > 1 {
// Count only those upcast versions that match the trait-ref
// we are looking for. Specifically, do not only check for the
// correct trait, but also the correct type parameters.
// For example, we may be trying to upcast `Foo` to `Bar<i32>`,
// but `Foo` is declared as `trait Foo : Bar<u32>`.
let upcast_trait_refs = util::supertraits(self.tcx(), poly_trait_ref)
.filter(|upcast_trait_ref| self.infcx.probe(|_| {
let upcast_trait_ref = upcast_trait_ref.clone();
self.match_poly_trait_ref(obligation, upcast_trait_ref).is_ok()
})).count();

if upcast_trait_refs > 1 {
// can be upcast in many ways; need more type information
candidates.ambiguous = true;
} else if upcast_trait_refs.len() == 1 {
} else if upcast_trait_refs == 1 {
candidates.vec.push(ObjectCandidate);
}

Expand Down Expand Up @@ -2305,20 +2314,28 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// be exactly one applicable trait-reference; if this were not
// the case, we would have reported an ambiguity error rather
// than successfully selecting one of the candidates.
let upcast_trait_refs = self.upcast(poly_trait_ref.clone(), obligation);
assert_eq!(upcast_trait_refs.len(), 1);
let upcast_trait_ref = upcast_trait_refs.into_iter().next().unwrap();
let mut upcast_trait_refs = util::supertraits(self.tcx(), poly_trait_ref)
.map(|upcast_trait_ref| {
(upcast_trait_ref.clone(), self.infcx.probe(|_| {
self.match_poly_trait_ref(obligation, upcast_trait_ref)
}).is_ok())
});
let mut upcast_trait_ref = None;
let mut vtable_base = 0;

match self.match_poly_trait_ref(obligation, upcast_trait_ref.clone()) {
Ok(()) => { }
Err(()) => {
self.tcx().sess.span_bug(obligation.cause.span,
"failed to match trait refs");
while let Some((supertrait, matches)) = upcast_trait_refs.next() {
if matches {
upcast_trait_ref = Some(supertrait);
break;
}
vtable_base += util::count_own_vtable_entries(self.tcx(), supertrait);
}
assert!(upcast_trait_refs.all(|(_, matches)| !matches));

VtableObjectData { object_ty: self_ty,
upcast_trait_ref: upcast_trait_ref }
VtableObjectData {
upcast_trait_ref: upcast_trait_ref.unwrap(),
vtable_base: vtable_base
}
}

fn confirm_fn_pointer_candidate(&mut self,
Expand Down Expand Up @@ -2719,7 +2736,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {

/// Returns `Ok` if `poly_trait_ref` being true implies that the
/// obligation is satisfied.
fn match_poly_trait_ref(&mut self,
fn match_poly_trait_ref(&self,
obligation: &TraitObligation<'tcx>,
poly_trait_ref: ty::PolyTraitRef<'tcx>)
-> Result<(),()>
Expand Down Expand Up @@ -2930,32 +2947,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
obligation.cause.clone()
}
}

/// Upcasts an object trait-reference into those that match the obligation.
fn upcast(&mut self, obj_trait_ref: ty::PolyTraitRef<'tcx>, obligation: &TraitObligation<'tcx>)
-> Vec<ty::PolyTraitRef<'tcx>>
{
debug!("upcast(obj_trait_ref={:?}, obligation={:?})",
obj_trait_ref,
obligation);

let obligation_def_id = obligation.predicate.def_id();
let mut upcast_trait_refs = util::upcast(self.tcx(), obj_trait_ref, obligation_def_id);

// Retain only those upcast versions that match the trait-ref
// we are looking for. In particular, we know that all of
// `upcast_trait_refs` apply to the correct trait, but
// possibly with incorrect type parameters. For example, we
// may be trying to upcast `Foo` to `Bar<i32>`, but `Foo` is
// declared as `trait Foo : Bar<u32>`.
upcast_trait_refs.retain(|upcast_trait_ref| {
let upcast_trait_ref = upcast_trait_ref.clone();
self.infcx.probe(|_| self.match_poly_trait_ref(obligation, upcast_trait_ref)).is_ok()
});

debug!("upcast: upcast_trait_refs={:?}", upcast_trait_refs);
upcast_trait_refs
}
}

impl<'tcx> SelectionCache<'tcx> {
Expand Down
67 changes: 32 additions & 35 deletions src/librustc/middle/traits/util.rs
Expand Up @@ -396,50 +396,45 @@ pub fn upcast<'tcx>(tcx: &ty::ctxt<'tcx>,
.collect()
}

/// Given an object of type `object_trait_ref`, returns the index of
/// the method `method_def_id` (which should be part of a supertrait
/// of `object_trait_ref`) within the vtable for `object_trait_ref`.
pub fn get_vtable_index_of_object_method<'tcx>(tcx: &ty::ctxt<'tcx>,
object_trait_ref: ty::PolyTraitRef<'tcx>,
method_def_id: ast::DefId) -> usize {
// We need to figure the "real index" of the method in a
// listing of all the methods of an object. We do this by
// iterating down the supertraits of the object's trait until
// we find the trait the method came from, counting up the
// methods from them.
let mut method_count = 0;

let trait_def_id = tcx.impl_or_trait_item(method_def_id).container().id();

for bound_ref in transitive_bounds(tcx, &[object_trait_ref]) {
if bound_ref.def_id() == trait_def_id {
break;
}

let trait_items = tcx.trait_items(bound_ref.def_id());
for trait_item in trait_items.iter() {
match *trait_item {
ty::MethodTraitItem(_) => method_count += 1,
_ => {}
}
/// Given an trait `trait_ref`, returns the number of vtable entries
/// that come from `trait_ref`, excluding its supertraits. Used in
/// computing the vtable base for an upcast trait of a trait object.
pub fn count_own_vtable_entries<'tcx>(tcx: &ty::ctxt<'tcx>,
trait_ref: ty::PolyTraitRef<'tcx>)
-> usize {
let mut entries = 0;
// Count number of methods and add them to the total offset.
// Skip over associated types and constants.
for trait_item in &tcx.trait_items(trait_ref.def_id())[..] {
if let ty::MethodTraitItem(_) = *trait_item {
entries += 1;
}
}
entries
}

// count number of methods preceding the one we are selecting and
// add them to the total offset; skip over associated types.
for trait_item in &tcx.trait_items(trait_def_id)[..] {
/// Given an upcast trait object described by `object`, returns the
/// index of the method `method_def_id` (which should be part of
/// `object.upcast_trait_ref`) within the vtable for `object`.
pub fn get_vtable_index_of_object_method<'tcx>(tcx: &ty::ctxt<'tcx>,
object: &super::VtableObjectData<'tcx>,
method_def_id: ast::DefId) -> usize {
// Count number of methods preceding the one we are selecting and
// add them to the total offset.
// Skip over associated types and constants.
let mut entries = object.vtable_base;
for trait_item in &tcx.trait_items(object.upcast_trait_ref.def_id())[..] {
if trait_item.def_id() == method_def_id {
// The item with the ID we were given really ought to be a method.
assert!(match *trait_item {
ty::MethodTraitItem(_) => true,
_ => false
});

return method_count;
return entries;
}
match *trait_item {
ty::MethodTraitItem(_) => method_count += 1,
_ => {}
if let ty::MethodTraitItem(_) = *trait_item {
entries += 1;
}
}

Expand Down Expand Up @@ -493,7 +488,7 @@ impl<'tcx, N:fmt::Debug> fmt::Debug for super::Vtable<'tcx, N> {
write!(f, "VtableFnPointer({:?})", d),

super::VtableObject(ref d) =>
write!(f, "VtableObject({:?})", d),
write!(f, "{:?}", d),

super::VtableParam(ref n) =>
write!(f, "VtableParam({:?})", n),
Expand Down Expand Up @@ -538,7 +533,9 @@ impl<'tcx, N:fmt::Debug> fmt::Debug for super::VtableDefaultImplData<N> {

impl<'tcx> fmt::Debug for super::VtableObjectData<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "VtableObject(object_ty={:?})", self.object_ty)
write!(f, "VtableObject(upcast={:?}, vtable_base={})",
self.upcast_trait_ref,
self.vtable_base)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/librustc/middle/ty_fold.rs
Expand Up @@ -492,8 +492,8 @@ impl<'tcx, N: TypeFoldable<'tcx>> TypeFoldable<'tcx> for traits::Vtable<'tcx, N>
impl<'tcx> TypeFoldable<'tcx> for traits::VtableObjectData<'tcx> {
fn fold_with<F:TypeFolder<'tcx>>(&self, folder: &mut F) -> traits::VtableObjectData<'tcx> {
traits::VtableObjectData {
object_ty: self.object_ty.fold_with(folder),
upcast_trait_ref: self.upcast_trait_ref.fold_with(folder),
vtable_base: self.vtable_base
}
}
}
Expand Down
18 changes: 2 additions & 16 deletions src/librustc_trans/trans/meth.rs
Expand Up @@ -160,20 +160,6 @@ pub fn trans_method_callee<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
}
}

fn method_offset_in_object_vtable<'tcx>(tcx: &ty::ctxt<'tcx>,
object_ty: Ty<'tcx>,
method_id: ast::DefId)
-> usize {
if let ty::TyTrait(ref data) = object_ty.sty {
let trait_ref = data.principal_trait_ref_with_self_ty(tcx, object_ty);
traits::get_vtable_index_of_object_method(tcx, trait_ref, method_id)
} else {
tcx.sess.bug(&format!(
"trans::methd::object_ty_to_trait_ref() called on non-object: {:?}",
object_ty));
}
}

pub fn trans_static_method_callee<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
method_id: ast::DefId,
trait_id: ast::DefId,
Expand Down Expand Up @@ -285,7 +271,7 @@ pub fn trans_static_method_callee<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
callee_substs)
}
traits::VtableObject(ref data) => {
let idx = method_offset_in_object_vtable(tcx, data.object_ty, method_id);
let idx = traits::get_vtable_index_of_object_method(tcx, data, method_id);
trans_object_shim(ccx,
data.upcast_trait_ref.clone(),
method_id,
Expand Down Expand Up @@ -384,7 +370,7 @@ fn trans_monomorphized_callee<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
}
}
traits::VtableObject(ref data) => {
let idx = method_offset_in_object_vtable(bcx.tcx(), data.object_ty, method_id);
let idx = traits::get_vtable_index_of_object_method(bcx.tcx(), data, method_id);
if let Some(self_expr) = self_expr {
if let ty::TyBareFn(_, ref fty) = monomorphize_type(bcx, method_ty).sty {
let ty = bcx.tcx().mk_fn(None, opaque_method_ty(bcx.tcx(), fty));
Expand Down
40 changes: 40 additions & 0 deletions src/test/run-pass/traits-issue-26339.rs
@@ -0,0 +1,40 @@
// 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.

// Test that the right implementation is called through a trait
// object when supertraits include multiple references to the
// same trait, with different type parameters.

trait A: PartialEq<Foo> + PartialEq<Bar> { }

struct Foo;
struct Bar;

struct Aimpl;

impl PartialEq<Foo> for Aimpl {
fn eq(&self, _rhs: &Foo) -> bool {
true
}
}

impl PartialEq<Bar> for Aimpl {
fn eq(&self, _rhs: &Bar) -> bool {
false
}
}

impl A for Aimpl { }

fn main() {
let a = &Aimpl as &A;

assert!(*a == Foo);
}

0 comments on commit 536e71b

Please sign in to comment.