From 536e71b78f46acb75e08481708a93bd07c0a107a Mon Sep 17 00:00:00 2001 From: Eduard Burtescu Date: Sat, 4 Jul 2015 05:46:54 +0300 Subject: [PATCH] rustc: compute the vtable base of a supertrait during selection. Fixes #26339. --- src/librustc/middle/traits/mod.rs | 8 ++- src/librustc/middle/traits/project.rs | 10 ++-- src/librustc/middle/traits/select.rs | 73 +++++++++++-------------- src/librustc/middle/traits/util.rs | 67 +++++++++++------------ src/librustc/middle/ty_fold.rs | 2 +- src/librustc_trans/trans/meth.rs | 18 +----- src/test/run-pass/traits-issue-26339.rs | 40 ++++++++++++++ 7 files changed, 117 insertions(+), 101 deletions(-) create mode 100644 src/test/run-pass/traits-issue-26339.rs diff --git a/src/librustc/middle/traits/mod.rs b/src/librustc/middle/traits/mod.rs index 5126a549887ed..b5f01ada7e178 100644 --- a/src/librustc/middle/traits/mod.rs +++ b/src/librustc/middle/traits/mod.rs @@ -291,11 +291,13 @@ pub struct VtableBuiltinData { /// 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. diff --git a/src/librustc/middle/traits/project.rs b/src/librustc/middle/traits/project.rs index e5f7542aa84c9..e29d9646509f9 100644 --- a/src/librustc/middle/traits/project.rs +++ b/src/librustc/middle/traits/project.rs @@ -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 { @@ -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( diff --git a/src/librustc/middle/traits/select.rs b/src/librustc/middle/traits/select.rs index abc300869adca..ad91f664af2db 100644 --- a/src/librustc/middle/traits/select.rs +++ b/src/librustc/middle/traits/select.rs @@ -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`, + // but `Foo` is declared as `trait Foo : Bar`. + 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); } @@ -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, @@ -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<(),()> @@ -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> - { - 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`, but `Foo` is - // declared as `trait Foo : Bar`. - 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> { diff --git a/src/librustc/middle/traits/util.rs b/src/librustc/middle/traits/util.rs index e8a24876b18dc..af9d5e5157d28 100644 --- a/src/librustc/middle/traits/util.rs +++ b/src/librustc/middle/traits/util.rs @@ -396,38 +396,34 @@ 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 { @@ -435,11 +431,10 @@ pub fn get_vtable_index_of_object_method<'tcx>(tcx: &ty::ctxt<'tcx>, _ => false }); - return method_count; + return entries; } - match *trait_item { - ty::MethodTraitItem(_) => method_count += 1, - _ => {} + if let ty::MethodTraitItem(_) = *trait_item { + entries += 1; } } @@ -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), @@ -538,7 +533,9 @@ impl<'tcx, N:fmt::Debug> fmt::Debug for super::VtableDefaultImplData { 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) } } diff --git a/src/librustc/middle/ty_fold.rs b/src/librustc/middle/ty_fold.rs index 402f31fc770d9..7016c1484659b 100644 --- a/src/librustc/middle/ty_fold.rs +++ b/src/librustc/middle/ty_fold.rs @@ -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>(&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 } } } diff --git a/src/librustc_trans/trans/meth.rs b/src/librustc_trans/trans/meth.rs index ece5682c6ba9b..2cab7d2a1c84a 100644 --- a/src/librustc_trans/trans/meth.rs +++ b/src/librustc_trans/trans/meth.rs @@ -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, @@ -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, @@ -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)); diff --git a/src/test/run-pass/traits-issue-26339.rs b/src/test/run-pass/traits-issue-26339.rs new file mode 100644 index 0000000000000..f2cf1779d6241 --- /dev/null +++ b/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 or the MIT license +// , 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 + PartialEq { } + +struct Foo; +struct Bar; + +struct Aimpl; + +impl PartialEq for Aimpl { + fn eq(&self, _rhs: &Foo) -> bool { + true + } +} + +impl PartialEq for Aimpl { + fn eq(&self, _rhs: &Bar) -> bool { + false + } +} + +impl A for Aimpl { } + +fn main() { + let a = &Aimpl as &A; + + assert!(*a == Foo); +}