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

Instantiate binders in supertrait_vtable_slot #131042

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
17 changes: 16 additions & 1 deletion compiler/rustc_trait_selection/src/traits/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ use rustc_infer::infer::canonical::{
Canonical, CanonicalQueryResponse, CanonicalVarValues, QueryResponse,
};
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk, RegionResolutionError};
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk, RegionResolutionError, TypeTrace};
use rustc_macros::extension;
use rustc_middle::arena::ArenaAllocatable;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, Upcast, Variance};
use rustc_type_ir::relate::Relate;

use super::{FromSolverError, FulfillmentContext, ScrubbedTraitError, TraitEngine};
use crate::error_reporting::InferCtxtErrorExt;
Expand Down Expand Up @@ -133,6 +134,20 @@ where
.map(|infer_ok| self.register_infer_ok_obligations(infer_ok))
}

pub fn eq_trace<T: Relate<TyCtxt<'tcx>>>(
&self,
cause: &ObligationCause<'tcx>,
param_env: ty::ParamEnv<'tcx>,
trace: TypeTrace<'tcx>,
expected: T,
actual: T,
) -> Result<(), TypeError<'tcx>> {
self.infcx
.at(cause, param_env)
.eq_trace(DefineOpaqueTypes::Yes, trace, expected, actual)
.map(|infer_ok| self.register_infer_ok_obligations(infer_ok))
}

/// Checks whether `expected` is a subtype of `actual`: `expected <: actual`.
pub fn sub<T: ToTrace<'tcx>>(
&self,
Expand Down
90 changes: 65 additions & 25 deletions compiler/rustc_trait_selection/src/traits/vtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use std::fmt::Debug;
use std::ops::ControlFlow;

use rustc_hir::def_id::DefId;
use rustc_infer::infer::at::ToTrace;
use rustc_infer::infer::{BoundRegionConversionTime, TyCtxtInferExt};
use rustc_infer::traits::ObligationCause;
use rustc_infer::traits::util::PredicateSet;
use rustc_middle::bug;
use rustc_middle::query::Providers;
Expand All @@ -13,7 +16,7 @@ use smallvec::{SmallVec, smallvec};
use tracing::debug;

use crate::errors::DumpVTableEntries;
use crate::traits::{impossible_predicates, is_vtable_safe_method};
use crate::traits::{ObligationCtxt, impossible_predicates, is_vtable_safe_method};

#[derive(Clone, Debug)]
pub enum VtblSegment<'tcx> {
Expand All @@ -22,6 +25,8 @@ pub enum VtblSegment<'tcx> {
}

/// Prepare the segments for a vtable
// FIXME: This should take a `PolyExistentialTraitRef`, since we don't care
// about our `Self` type here.
pub fn prepare_vtable_segments<'tcx, T>(
tcx: TyCtxt<'tcx>,
trait_ref: ty::PolyTraitRef<'tcx>,
Expand Down Expand Up @@ -327,14 +332,10 @@ pub(crate) fn first_method_vtable_slot<'tcx>(tcx: TyCtxt<'tcx>, key: ty::TraitRe
let ty::Dynamic(source, _, _) = *key.self_ty().kind() else {
bug!();
};
let source_principal = tcx
.normalize_erasing_regions(ty::ParamEnv::reveal_all(), source.principal().unwrap())
.with_self_ty(tcx, tcx.types.trait_object_dummy_self);
let source_principal =
source.principal().unwrap().with_self_ty(tcx, tcx.types.trait_object_dummy_self);

let target_principal = tcx
.normalize_erasing_regions(ty::ParamEnv::reveal_all(), key)
// We don't care about the self type, since it will always be the same thing.
.with_self_ty(tcx, tcx.types.trait_object_dummy_self);
let target_principal = ty::Binder::dummy(ty::ExistentialTraitRef::erase_self_ty(tcx, key));

let vtable_segment_callback = {
let mut vptr_offset = 0;
Expand All @@ -343,15 +344,18 @@ pub(crate) fn first_method_vtable_slot<'tcx>(tcx: TyCtxt<'tcx>, key: ty::TraitRe
VtblSegment::MetadataDSA => {
vptr_offset += TyCtxt::COMMON_VTABLE_ENTRIES.len();
}
VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
if tcx
.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), trait_ref)
== target_principal
{
VtblSegment::TraitOwnEntries { trait_ref: vtable_principal, emit_vptr } => {
if trait_refs_are_compatible(
tcx,
vtable_principal
.map_bound(|t| ty::ExistentialTraitRef::erase_self_ty(tcx, t)),
target_principal,
) {
return ControlFlow::Break(vptr_offset);
}

vptr_offset += tcx.own_existential_vtable_entries(trait_ref.def_id()).len();
vptr_offset +=
tcx.own_existential_vtable_entries(vtable_principal.def_id()).len();

if emit_vptr {
vptr_offset += 1;
Expand Down Expand Up @@ -383,17 +387,14 @@ pub(crate) fn supertrait_vtable_slot<'tcx>(
let ty::Dynamic(target, _, _) = *target.kind() else {
bug!();
};
let target_principal = tcx
.normalize_erasing_regions(ty::ParamEnv::reveal_all(), target.principal()?)
.with_self_ty(tcx, tcx.types.trait_object_dummy_self);
let target_principal = target.principal()?;

// Given that we have a target principal, it is a bug for there not to be a source principal.
let ty::Dynamic(source, _, _) = *source.kind() else {
bug!();
};
let source_principal = tcx
.normalize_erasing_regions(ty::ParamEnv::reveal_all(), source.principal().unwrap())
.with_self_ty(tcx, tcx.types.trait_object_dummy_self);
let source_principal =
source.principal().unwrap().with_self_ty(tcx, tcx.types.trait_object_dummy_self);

let vtable_segment_callback = {
let mut vptr_offset = 0;
Expand All @@ -402,11 +403,15 @@ pub(crate) fn supertrait_vtable_slot<'tcx>(
VtblSegment::MetadataDSA => {
vptr_offset += TyCtxt::COMMON_VTABLE_ENTRIES.len();
}
VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
vptr_offset += tcx.own_existential_vtable_entries(trait_ref.def_id()).len();
if tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), trait_ref)
== target_principal
{
VtblSegment::TraitOwnEntries { trait_ref: vtable_principal, emit_vptr } => {
vptr_offset +=
tcx.own_existential_vtable_entries(vtable_principal.def_id()).len();
if trait_refs_are_compatible(
tcx,
vtable_principal
.map_bound(|t| ty::ExistentialTraitRef::erase_self_ty(tcx, t)),
target_principal,
) {
if emit_vptr {
return ControlFlow::Break(Some(vptr_offset));
} else {
Expand All @@ -426,6 +431,41 @@ pub(crate) fn supertrait_vtable_slot<'tcx>(
prepare_vtable_segments(tcx, source_principal, vtable_segment_callback).unwrap()
}

fn trait_refs_are_compatible<'tcx>(
tcx: TyCtxt<'tcx>,
hr_vtable_principal: ty::PolyExistentialTraitRef<'tcx>,
hr_target_principal: ty::PolyExistentialTraitRef<'tcx>,
) -> bool {
if hr_vtable_principal.def_id() != hr_target_principal.def_id() {
return false;
}

let infcx = tcx.infer_ctxt().build();
let param_env = ty::ParamEnv::reveal_all();
let ocx = ObligationCtxt::new(&infcx);
let hr_source_principal =
ocx.normalize(&ObligationCause::dummy(), param_env, hr_vtable_principal);
let hr_target_principal =
ocx.normalize(&ObligationCause::dummy(), param_env, hr_target_principal);
infcx.enter_forall(hr_target_principal, |target_principal| {
let source_principal = infcx.instantiate_binder_with_fresh_vars(
DUMMY_SP,
BoundRegionConversionTime::HigherRankedType,
hr_source_principal,
);
let Ok(()) = ocx.eq_trace(
&ObligationCause::dummy(),
param_env,
ToTrace::to_trace(&ObligationCause::dummy(), hr_target_principal, hr_source_principal),
target_principal,
source_principal,
) else {
return false;
};
ocx.select_all_or_error().is_empty()
})
}

pub(super) fn provide(providers: &mut Providers) {
*providers = Providers {
own_existential_vtable_entries,
Expand Down
15 changes: 8 additions & 7 deletions tests/ui/traits/trait-upcasting/higher-ranked-upcasting-ok.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
//@ check-pass
//@ build-pass

// We should be able to instantiate a binder during trait upcasting.
// This test could be `check-pass`, but we should make sure that we
// do so in both trait solvers.
// Check that we are able to instantiate a binder during trait upcasting,
// and that it doesn't cause any issues with codegen either.

#![feature(trait_upcasting)]

trait Supertrait<'a, 'b> {}
trait Subtrait<'a, 'b>: Supertrait<'a, 'b> {}

impl<'a> Supertrait<'a, 'a> for () {}
impl<'a> Subtrait<'a, 'a> for () {}
impl Supertrait<'_, '_> for () {}
impl Subtrait<'_, '_> for () {}
fn ok(x: &dyn for<'a, 'b> Subtrait<'a, 'b>) -> &dyn for<'a> Supertrait<'a, 'a> {
x
}

fn main() {}
fn main() {
ok(&());
}
Loading