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

Trait upcasting #60900

Open
wants to merge 12 commits into
base: master
from
@@ -0,0 +1,26 @@
# `trait_upcasting`

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

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

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

The `trait_upcasting` feature adds support for trait upcasting. This allows a
trait object of type `dyn Foo` to be cast to a trait object of type `dyn Bar`
so long as `Foo: Bar`.

```rust,edition2018
#![feature(trait_upcasting)]
trait Foo {}
trait Bar: Foo {}
impl Foo for i32 {}
impl<T: Foo + ?Sized> Bar for T {}
let foo: &dyn Foo = &123;
let bar: &dyn Bar = foo;
```
@@ -1424,6 +1424,12 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
if let Some(exp_found) = exp_found {
self.suggest_as_ref_where_appropriate(span, &exp_found, diag);

if let &TypeError::Traits(ref exp_found_traits) = terr {
self.note_enable_trait_upcasting_where_appropriate(
&exp_found_traits, diag
);
}
}

// In some (most?) cases cause.body_id points to actual body, but in some cases
@@ -1508,6 +1514,24 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}

/// When encountering a coercion to a supertrait object is attempted,
/// note that this is permitted with the "trait upcasting" feature enabled.
fn note_enable_trait_upcasting_where_appropriate(
&self,
exp_found: &ty::error::ExpectedFound<DefId>,
diag: &mut DiagnosticBuilder<'tcx>,
) {
use rustc::ty::{Binder, TraitRef};

let trait_ref = Binder::bind(TraitRef::identity(self.tcx, exp_found.found));
let supertraits = crate::traits::supertraits(self.tcx, trait_ref);
if supertraits.into_iter().any(|trait_ref| trait_ref.def_id() == exp_found.expected) {
diag.note(
"add `#![feature(trait_upcasting)]` to the crate attributes to enable \
trait upcasting");
}
}

pub fn report_and_explain_type_error(
&self,
trace: TypeTrace<'tcx>,
@@ -553,7 +553,8 @@ rustc_queries! {

Other {
query vtable_methods(key: ty::PolyTraitRef<'tcx>)
-> &'tcx [Option<(DefId, SubstsRef<'tcx>)>] {
-> &'tcx [&'tcx [Option<(DefId, SubstsRef<'tcx>)>]]
{
no_force
desc { |tcx| "finding all methods for trait {}", tcx.def_path_str(key.def_id()) }
}
@@ -1246,6 +1246,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
"generate comments into the assembly (may change behavior)"),
verify_llvm_ir: bool = (false, parse_bool, [TRACKED],
"verify LLVM IR"),
no_verify_llvm_ir: bool = (false, parse_bool, [TRACKED],
"skip verifying LLVM IR (useful if it has been enabled via config.toml)"),
borrowck_stats: bool = (false, parse_bool, [UNTRACKED],
"gather borrowck statistics"),
no_landing_pads: bool = (false, parse_bool, [TRACKED],
@@ -456,8 +456,9 @@ impl Session {
self.opts.debugging_opts.asm_comments
}
pub fn verify_llvm_ir(&self) -> bool {
self.opts.debugging_opts.verify_llvm_ir
|| cfg!(always_verify_llvm_ir)
(self.opts.debugging_opts.verify_llvm_ir
|| cfg!(always_verify_llvm_ir))
&& !self.opts.debugging_opts.no_verify_llvm_ir
}
pub fn borrowck_stats(&self) -> bool {
self.opts.debugging_opts.borrowck_stats
@@ -61,7 +61,8 @@ pub use self::specialize::specialization_graph::FutureCompatOverlapErrorKind;
pub use self::engine::{TraitEngine, TraitEngineExt};
pub use self::util::{elaborate_predicates, elaborate_trait_ref, elaborate_trait_refs};
pub use self::util::{
supertraits, supertrait_def_ids, transitive_bounds, Supertraits, SupertraitDefIds,
supertraits, supertraits_with_repetitions, supertrait_def_ids, transitive_bounds,
Supertraits, SupertraitDefIds,
};
pub use self::util::{expand_trait_aliases, TraitAliasExpander};

@@ -1020,65 +1021,66 @@ fn substitute_normalize_and_test_predicates<'tcx>(
result
}

/// Given a trait `trait_ref`, iterates the vtable entries
/// that come from `trait_ref`, including its supertraits.
/// Given a trait `trait_ref`, iterates the vtable entries that come from `trait_ref`, including its
/// supertraits, and returns them per-trait.
#[inline] // FIXME(#35870): avoid closures being unexported due to `impl Trait`.
fn vtable_methods<'tcx>(
tcx: TyCtxt<'tcx>,
trait_ref: ty::PolyTraitRef<'tcx>,
) -> &'tcx [Option<(DefId, SubstsRef<'tcx>)>] {
) -> &'tcx [&'tcx [Option<(DefId, SubstsRef<'tcx>)>]] {
debug!("vtable_methods({:?})", trait_ref);

tcx.arena.alloc_from_iter(
supertraits(tcx, trait_ref).flat_map(move |trait_ref| {
let trait_methods = tcx.associated_items(trait_ref.def_id())
.filter(|item| item.kind == ty::AssocKind::Method);

// Now list each method's DefId and InternalSubsts (for within its trait).
// If the method can never be called from this object, produce None.
trait_methods.map(move |trait_method| {
debug!("vtable_methods: trait_method={:?}", trait_method);
let def_id = trait_method.def_id;

// Some methods cannot be called on an object; skip those.
if !tcx.is_vtable_safe_method(trait_ref.def_id(), &trait_method) {
debug!("vtable_methods: not vtable safe");
return None;
}

// The method may have some early-bound lifetimes; add regions for those.
let substs = trait_ref.map_bound(|trait_ref|
InternalSubsts::for_item(tcx, def_id, |param, _|
match param.kind {
GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
GenericParamDefKind::Type { .. } |
GenericParamDefKind::Const => {
trait_ref.substs[param.index as usize]
supertraits_with_repetitions(tcx, trait_ref)
.map(move |trait_ref| {
let trait_methods = tcx.associated_items(trait_ref.def_id())
.filter(|item| item.kind == ty::AssocKind::Method);

// Now list each method's `DefId` and `InternalSubsts` (for within its trait).
// If the method can never be called from this object, produce `None`.
&*tcx.arena.alloc_from_iter(trait_methods.map(move |trait_method| {
debug!("vtable_methods: trait_method={:?}", trait_method);
let def_id = trait_method.def_id;

// Some methods cannot be called on an object; skip those.
if !tcx.is_vtable_safe_method(trait_ref.def_id(), &trait_method) {
debug!("vtable_methods: not vtable safe");
return None;
}

// The method may have some early-bound lifetimes; add regions for those.
let substs = trait_ref.map_bound(|trait_ref|
InternalSubsts::for_item(tcx, def_id, |param, _|
match param.kind {
GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
GenericParamDefKind::Type { .. } |
GenericParamDefKind::Const => {
trait_ref.substs[param.index as usize]
}
}
}
)
);

// The trait type may have higher-ranked lifetimes in it;
// erase them if they appear, so that we get the type
// at some particular call site.
let substs = tcx.normalize_erasing_late_bound_regions(
ty::ParamEnv::reveal_all(),
&substs
);

// It's possible that the method relies on where-clauses that
// do not hold for this particular set of type parameters.
// Note that this method could then never be called, so we
// do not want to try and codegen it, in that case (see #23435).
let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs);
if !normalize_and_test_predicates(tcx, predicates.predicates) {
debug!("vtable_methods: predicates do not hold");
return None;
}

Some((def_id, substs))
})
)
);

// The trait type may have higher-ranked lifetimes in it;
// erase them if they appear, so that we get the type
// at some particular call site.
let substs = tcx.normalize_erasing_late_bound_regions(
ty::ParamEnv::reveal_all(),
&substs
);

// It's possible that the method relies on where-clauses that
// do not hold for this particular set of type parameters.
// Note that this method could then never be called, so we
// do not want to try and codegen it, in that case (see #23435).
let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs);
if !normalize_and_test_predicates(tcx, predicates.predicates) {
debug!("vtable_methods: predicates do not hold");
return None;
}

Some((def_id, substs))
}))
})
)
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.