about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2023-07-26 23:54:55 +0000
committerMichael Goulet <michael@errs.io>2023-08-03 18:21:11 +0000
commit1bb6ae5874730933188fe4be56b3a2f3d5a66962 (patch)
tree5f87e3381ebcd35f2df325b80b7f35e215aaa703
parentfcf3006e0133365ecd26894689c086387edcbecb (diff)
downloadrust-1bb6ae5874730933188fe4be56b3a2f3d5a66962.tar.gz
rust-1bb6ae5874730933188fe4be56b3a2f3d5a66962.zip
Rework upcasting
-rw-r--r--compiler/rustc_infer/src/infer/at.rs28
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs8
-rw-r--r--compiler/rustc_infer/src/infer/mod.rs2
-rw-r--r--compiler/rustc_middle/src/traits/solve/inspect.rs13
-rw-r--r--compiler/rustc_middle/src/traits/solve/inspect/format.rs7
-rw-r--r--compiler/rustc_middle/src/ty/print/pretty.rs3
-rw-r--r--compiler/rustc_trait_selection/src/solve/trait_goals.rs125
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/confirmation.rs139
-rw-r--r--tests/ui/traits/trait-upcasting/fewer-associated.rs25
-rw-r--r--tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.current.stderr14
-rw-r--r--tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.next.stderr14
-rw-r--r--tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.rs23
12 files changed, 297 insertions, 104 deletions
diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs
index 433735e827b..6d5db3336cf 100644
--- a/compiler/rustc_infer/src/infer/at.rs
+++ b/compiler/rustc_infer/src/infer/at.rs
@@ -481,3 +481,31 @@ impl<'tcx> ToTrace<'tcx> for ty::FnSig<'tcx> {
         TypeTrace { cause: cause.clone(), values: Sigs(ExpectedFound::new(a_is_expected, a, b)) }
     }
 }
+
+impl<'tcx> ToTrace<'tcx> for ty::PolyExistentialTraitRef<'tcx> {
+    fn to_trace(
+        cause: &ObligationCause<'tcx>,
+        a_is_expected: bool,
+        a: Self,
+        b: Self,
+    ) -> TypeTrace<'tcx> {
+        TypeTrace {
+            cause: cause.clone(),
+            values: ExistentialTraitRef(ExpectedFound::new(a_is_expected, a, b)),
+        }
+    }
+}
+
+impl<'tcx> ToTrace<'tcx> for ty::PolyExistentialProjection<'tcx> {
+    fn to_trace(
+        cause: &ObligationCause<'tcx>,
+        a_is_expected: bool,
+        a: Self,
+        b: Self,
+    ) -> TypeTrace<'tcx> {
+        TypeTrace {
+            cause: cause.clone(),
+            values: ExistentialProjection(ExpectedFound::new(a_is_expected, a, b)),
+        }
+    }
+}
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index 25077f9bb97..2b420bd869b 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -1635,6 +1635,12 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                         (false, Mismatch::Fixed(self.tcx.def_descr(expected.def_id)))
                     }
                     ValuePairs::Regions(_) => (false, Mismatch::Fixed("lifetime")),
+                    ValuePairs::ExistentialTraitRef(_) => {
+                        (false, Mismatch::Fixed("existential trait ref"))
+                    }
+                    ValuePairs::ExistentialProjection(_) => {
+                        (false, Mismatch::Fixed("existential projection"))
+                    }
                 };
                 let Some(vals) = self.values_str(values) else {
                     // Derived error. Cancel the emitter.
@@ -2139,6 +2145,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             infer::Regions(exp_found) => self.expected_found_str(exp_found),
             infer::Terms(exp_found) => self.expected_found_str_term(exp_found),
             infer::Aliases(exp_found) => self.expected_found_str(exp_found),
+            infer::ExistentialTraitRef(exp_found) => self.expected_found_str(exp_found),
+            infer::ExistentialProjection(exp_found) => self.expected_found_str(exp_found),
             infer::TraitRefs(exp_found) => {
                 let pretty_exp_found = ty::error::ExpectedFound {
                     expected: exp_found.expected.print_only_trait_path(),
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index a73e84f94c4..aaabf1482e2 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -374,6 +374,8 @@ pub enum ValuePairs<'tcx> {
     TraitRefs(ExpectedFound<ty::TraitRef<'tcx>>),
     PolyTraitRefs(ExpectedFound<ty::PolyTraitRef<'tcx>>),
     Sigs(ExpectedFound<ty::FnSig<'tcx>>),
+    ExistentialTraitRef(ExpectedFound<ty::PolyExistentialTraitRef<'tcx>>),
+    ExistentialProjection(ExpectedFound<ty::PolyExistentialProjection<'tcx>>),
 }
 
 impl<'tcx> ValuePairs<'tcx> {
diff --git a/compiler/rustc_middle/src/traits/solve/inspect.rs b/compiler/rustc_middle/src/traits/solve/inspect.rs
index e793f481995..4e2af3816ac 100644
--- a/compiler/rustc_middle/src/traits/solve/inspect.rs
+++ b/compiler/rustc_middle/src/traits/solve/inspect.rs
@@ -73,12 +73,15 @@ pub struct GoalCandidate<'tcx> {
 pub enum CandidateKind<'tcx> {
     /// Probe entered when normalizing the self ty during candidate assembly
     NormalizedSelfTyAssembly,
-    DynUpcastingAssembly,
     /// A normal candidate for proving a goal
-    Candidate {
-        name: String,
-        result: QueryResult<'tcx>,
-    },
+    Candidate { name: String, result: QueryResult<'tcx> },
+    /// Used in the probe that wraps normalizing the non-self type for the unsize
+    /// trait, which is also structurally matched on.
+    UnsizeAssembly,
+    /// During upcasting from some source object to target object type, used to
+    /// do a probe to find out what projection type(s) may be used to prove that
+    /// the source type upholds all of the target type's object bounds.
+    UpcastProbe,
 }
 impl Debug for GoalCandidate<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
diff --git a/compiler/rustc_middle/src/traits/solve/inspect/format.rs b/compiler/rustc_middle/src/traits/solve/inspect/format.rs
index f1e567e9bf8..8759fecb05a 100644
--- a/compiler/rustc_middle/src/traits/solve/inspect/format.rs
+++ b/compiler/rustc_middle/src/traits/solve/inspect/format.rs
@@ -100,8 +100,11 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
             CandidateKind::NormalizedSelfTyAssembly => {
                 writeln!(self.f, "NORMALIZING SELF TY FOR ASSEMBLY:")
             }
-            CandidateKind::DynUpcastingAssembly => {
-                writeln!(self.f, "ASSEMBLING CANDIDATES FOR DYN UPCASTING:")
+            CandidateKind::UnsizeAssembly => {
+                writeln!(self.f, "ASSEMBLING CANDIDATES FOR UNSIZING:")
+            }
+            CandidateKind::UpcastProbe => {
+                writeln!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:")
             }
             CandidateKind::Candidate { name, result } => {
                 writeln!(self.f, "CANDIDATE {name}: {result:?}")
diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs
index 6e68022031a..d3fd49150ba 100644
--- a/compiler/rustc_middle/src/ty/print/pretty.rs
+++ b/compiler/rustc_middle/src/ty/print/pretty.rs
@@ -2734,8 +2734,9 @@ forward_display_to_print! {
     // HACK(eddyb) these are exhaustive instead of generic,
     // because `for<'tcx>` isn't possible yet.
     ty::PolyExistentialPredicate<'tcx>,
+    ty::PolyExistentialProjection<'tcx>,
+    ty::PolyExistentialTraitRef<'tcx>,
     ty::Binder<'tcx, ty::TraitRef<'tcx>>,
-    ty::Binder<'tcx, ty::ExistentialTraitRef<'tcx>>,
     ty::Binder<'tcx, TraitRefPrintOnlyTraitPath<'tcx>>,
     ty::Binder<'tcx, TraitRefPrintOnlyTraitName<'tcx>>,
     ty::Binder<'tcx, ty::FnSig<'tcx>>,
diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
index 14a5b9656e5..41565fe5dd9 100644
--- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs
+++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
@@ -444,7 +444,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
             Err(NoSolution) => vec![],
         };
 
-        ecx.probe(|_| CandidateKind::DynUpcastingAssembly).enter(|ecx| {
+        ecx.probe(|_| CandidateKind::UnsizeAssembly).enter(|ecx| {
             let a_ty = goal.predicate.self_ty();
             // We need to normalize the b_ty since it's matched structurally
             // in the other functions below.
@@ -526,7 +526,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         b_region: ty::Region<'tcx>,
     ) -> Vec<(CanonicalResponse<'tcx>, BuiltinImplSource)> {
         let tcx = self.tcx();
-        let Goal { predicate: (a_ty, b_ty), .. } = goal;
+        let Goal { predicate: (a_ty, _b_ty), .. } = goal;
 
         // All of a's auto traits need to be in b's auto traits.
         let auto_traits_compatible =
@@ -535,51 +535,30 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
             return vec![];
         }
 
-        // Try to match `a_ty` against `b_ty`, replacing `a_ty`'s principal trait ref with
-        // the supertrait principal and subtyping the types.
-        let unsize_dyn_to_principal =
-            |ecx: &mut Self, principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
-                ecx.probe_candidate("upcast dyn to principle").enter(
-                    |ecx| -> Result<_, NoSolution> {
-                        // Require that all of the trait predicates from A match B, except for
-                        // the auto traits. We do this by constructing a new A type with B's
-                        // auto traits, and equating these types.
-                        let new_a_data = principal
-                            .into_iter()
-                            .map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait))
-                            .chain(a_data.iter().filter(|a| {
-                                matches!(a.skip_binder(), ty::ExistentialPredicate::Projection(_))
-                            }))
-                            .chain(
-                                b_data
-                                    .auto_traits()
-                                    .map(ty::ExistentialPredicate::AutoTrait)
-                                    .map(ty::Binder::dummy),
-                            );
-                        let new_a_data = tcx.mk_poly_existential_predicates_from_iter(new_a_data);
-                        let new_a_ty = Ty::new_dynamic(tcx, new_a_data, b_region, ty::Dyn);
-
-                        // We also require that A's lifetime outlives B's lifetime.
-                        ecx.eq(goal.param_env, new_a_ty, b_ty)?;
-                        ecx.add_goal(goal.with(tcx, ty::OutlivesPredicate(a_region, b_region)));
-                        ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
-                    },
-                )
-            };
-
         let mut responses = vec![];
         // If the principal def ids match (or are both none), then we're not doing
         // trait upcasting. We're just removing auto traits (or shortening the lifetime).
         if a_data.principal_def_id() == b_data.principal_def_id() {
-            if let Ok(resp) = unsize_dyn_to_principal(self, a_data.principal()) {
+            if let Ok(resp) = self.consider_builtin_upcast_to_principal(
+                goal,
+                a_data,
+                a_region,
+                b_data,
+                b_region,
+                a_data.principal(),
+            ) {
                 responses.push((resp, BuiltinImplSource::Misc));
             }
         } else if let Some(a_principal) = a_data.principal() {
             self.walk_vtable(
                 a_principal.with_self_ty(tcx, a_ty),
                 |ecx, new_a_principal, _, vtable_vptr_slot| {
-                    if let Ok(resp) = unsize_dyn_to_principal(
-                        ecx,
+                    if let Ok(resp) = ecx.consider_builtin_upcast_to_principal(
+                        goal,
+                        a_data,
+                        a_region,
+                        b_data,
+                        b_region,
                         Some(new_a_principal.map_bound(|trait_ref| {
                             ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
                         })),
@@ -631,6 +610,78 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
     }
 
+    fn consider_builtin_upcast_to_principal(
+        &mut self,
+        goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
+        a_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
+        a_region: ty::Region<'tcx>,
+        b_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
+        b_region: ty::Region<'tcx>,
+        upcast_principal: Option<ty::PolyExistentialTraitRef<'tcx>>,
+    ) -> QueryResult<'tcx> {
+        let param_env = goal.param_env;
+
+        // More than one projection in a_ty's bounds may match the projection
+        // in b_ty's bound. Use this to first determine *which* apply without
+        // having any inference side-effects. We process obligations because
+        // unification may initially succeed due to deferred projection equality.
+        let projection_may_match = |ecx: &mut Self, source_projection, target_projection| {
+            ecx.probe(|_| CandidateKind::UpcastProbe)
+                .enter(|ecx| -> Result<(), NoSolution> {
+                    ecx.eq(param_env, source_projection, target_projection)?;
+                    let _ = ecx.try_evaluate_added_goals()?;
+                    Ok(())
+                })
+                .is_ok()
+        };
+
+        for bound in b_data {
+            match bound.skip_binder() {
+                // Check that a's supertrait (upcast_principal) is compatible
+                // with the target (b_ty).
+                ty::ExistentialPredicate::Trait(target_principal) => {
+                    self.eq(param_env, upcast_principal.unwrap(), bound.rebind(target_principal))?;
+                }
+                // Check that b_ty's projection is satisfied by exactly one of
+                // a_ty's projections. First, we look through the list to see if
+                // any match. If not, error. Then, if *more* than one matches, we
+                // return ambiguity. Otherwise, if exactly one matches, equate
+                // it with b_ty's projection.
+                ty::ExistentialPredicate::Projection(target_projection) => {
+                    let target_projection = bound.rebind(target_projection);
+                    let mut matching_projections =
+                        a_data.projection_bounds().filter(|source_projection| {
+                            projection_may_match(self, *source_projection, target_projection)
+                        });
+                    let Some(source_projection) = matching_projections.next() else {
+                        return Err(NoSolution);
+                    };
+                    if matching_projections.next().is_some() {
+                        return self.evaluate_added_goals_and_make_canonical_response(
+                            Certainty::AMBIGUOUS,
+                        );
+                    }
+                    self.eq(param_env, source_projection, target_projection)?;
+                }
+                // Check that b_ty's auto traits are present in a_ty's bounds.
+                ty::ExistentialPredicate::AutoTrait(def_id) => {
+                    if !a_data.auto_traits().any(|source_def_id| source_def_id == def_id) {
+                        return Err(NoSolution);
+                    }
+                }
+            }
+        }
+
+        // Also require that a_ty's lifetime outlives b_ty's lifetime.
+        self.add_goal(Goal::new(
+            self.tcx(),
+            param_env,
+            ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)),
+        ));
+
+        self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
+    }
+
     /// We have the following builtin impls for arrays:
     /// ```ignore (builtin impl example)
     /// impl<T: ?Sized, const N: usize> Unsize<[T]> for [T; N] {}
diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
index bf09681c66d..77304984402 100644
--- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
@@ -879,68 +879,89 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
 
         // `assemble_candidates_for_unsizing` should ensure there are no late-bound
         // regions here. See the comment there for more details.
-        let source = self.infcx.shallow_resolve(obligation.self_ty().no_bound_vars().unwrap());
-        let target = obligation.predicate.skip_binder().trait_ref.args.type_at(1);
-        let target = self.infcx.shallow_resolve(target);
+        let predicate = obligation.predicate.no_bound_vars().unwrap();
+        let a_ty = self.infcx.shallow_resolve(predicate.self_ty());
+        let b_ty = self.infcx.shallow_resolve(predicate.trait_ref.args.type_at(1));
+
+        let ty::Dynamic(a_data, a_region, ty::Dyn) = *a_ty.kind() else { bug!() };
+        let ty::Dynamic(b_data, b_region, ty::Dyn) = *b_ty.kind() else { bug!() };
 
-        debug!(?source, ?target, "confirm_trait_upcasting_unsize_candidate");
+        let source_principal = a_data.principal().unwrap().with_self_ty(tcx, a_ty);
+        let upcast_principal = util::supertraits(tcx, source_principal).nth(idx).unwrap();
 
         let mut nested = vec![];
-        let source_trait_ref;
-        let upcast_trait_ref;
-        match (source.kind(), target.kind()) {
-            // TraitA+Kx+'a -> TraitB+Ky+'b (trait upcasting coercion).
-            (
-                &ty::Dynamic(ref data_a, r_a, repr_a @ ty::Dyn),
-                &ty::Dynamic(ref data_b, r_b, ty::Dyn),
-            ) => {
-                // See `assemble_candidates_for_unsizing` for more info.
-                // We already checked the compatibility of auto traits within `assemble_candidates_for_unsizing`.
-                let principal_a = data_a.principal().unwrap();
-                source_trait_ref = principal_a.with_self_ty(tcx, source);
-                upcast_trait_ref = util::supertraits(tcx, source_trait_ref).nth(idx).unwrap();
-                assert_eq!(data_b.principal_def_id(), Some(upcast_trait_ref.def_id()));
-                let existential_predicate = upcast_trait_ref.map_bound(|trait_ref| {
-                    ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(
-                        tcx, trait_ref,
-                    ))
-                });
-                let iter = Some(existential_predicate)
-                    .into_iter()
-                    .chain(
-                        data_a
-                            .projection_bounds()
-                            .map(|b| b.map_bound(ty::ExistentialPredicate::Projection)),
-                    )
-                    .chain(
-                        data_b
-                            .auto_traits()
-                            .map(ty::ExistentialPredicate::AutoTrait)
-                            .map(ty::Binder::dummy),
+        for bound in b_data {
+            match bound.skip_binder() {
+                // Check that a's supertrait (upcast_principal) is compatible
+                // with the target (b_ty).
+                ty::ExistentialPredicate::Trait(target_principal) => {
+                    nested.extend(
+                        self.infcx
+                            .at(&obligation.cause, obligation.param_env)
+                            .sup(
+                                DefineOpaqueTypes::No,
+                                upcast_principal.map_bound(|trait_ref| {
+                                    ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
+                                }),
+                                bound.rebind(target_principal),
+                            )
+                            .map_err(|_| SelectionError::Unimplemented)?
+                            .into_obligations(),
                     );
-                let existential_predicates = tcx.mk_poly_existential_predicates_from_iter(iter);
-                let source_trait = Ty::new_dynamic(tcx, existential_predicates, r_b, repr_a);
-
-                // Require that the traits involved in this upcast are **equal**;
-                // only the **lifetime bound** is changed.
-                let InferOk { obligations, .. } = self
-                    .infcx
-                    .at(&obligation.cause, obligation.param_env)
-                    .sup(DefineOpaqueTypes::No, target, source_trait)
-                    .map_err(|_| Unimplemented)?;
-                nested.extend(obligations);
-
-                let outlives = ty::OutlivesPredicate(r_a, r_b);
-                nested.push(Obligation::with_depth(
-                    tcx,
-                    obligation.cause.clone(),
-                    obligation.recursion_depth + 1,
-                    obligation.param_env,
-                    obligation.predicate.rebind(outlives),
-                ));
+                }
+                // Check that b_ty's projection is satisfied by exactly one of
+                // a_ty's projections. First, we look through the list to see if
+                // any match. If not, error. Then, if *more* than one matches, we
+                // return ambiguity. Otherwise, if exactly one matches, equate
+                // it with b_ty's projection.
+                ty::ExistentialPredicate::Projection(target_projection) => {
+                    let target_projection = bound.rebind(target_projection);
+                    let mut matching_projections =
+                        a_data.projection_bounds().filter(|source_projection| {
+                            // Eager normalization means that we can just use can_eq
+                            // here instead of equating and processing obligations.
+                            self.infcx.can_eq(
+                                obligation.param_env,
+                                *source_projection,
+                                target_projection,
+                            )
+                        });
+                    let Some(source_projection) = matching_projections.next() else {
+                        return Err(SelectionError::Unimplemented);
+                    };
+                    if matching_projections.next().is_some() {
+                        // This is incomplete but I don't care. We should never
+                        // have more than one projection that ever applies with
+                        // eager norm and actually implementable traits, since
+                        // you can't have two supertraits like:
+                        // `trait A: B<i32, Assoc = First> + B<i32, Assoc = Second>`
+                        return Err(SelectionError::Unimplemented);
+                    }
+                    nested.extend(
+                        self.infcx
+                            .at(&obligation.cause, obligation.param_env)
+                            .sup(DefineOpaqueTypes::No, source_projection, target_projection)
+                            .map_err(|_| SelectionError::Unimplemented)?
+                            .into_obligations(),
+                    );
+                }
+                // Check that b_ty's auto trait is present in a_ty's bounds.
+                ty::ExistentialPredicate::AutoTrait(def_id) => {
+                    if !a_data.auto_traits().any(|source_def_id| source_def_id == def_id) {
+                        return Err(SelectionError::Unimplemented);
+                    }
+                }
             }
-            _ => bug!(),
-        };
+        }
+
+        // Also require that a_ty's lifetime outlives b_ty's lifetime.
+        nested.push(Obligation::with_depth(
+            tcx,
+            obligation.cause.clone(),
+            obligation.recursion_depth + 1,
+            obligation.param_env,
+            ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)),
+        ));
 
         let vtable_segment_callback = {
             let mut vptr_offset = 0;
@@ -951,7 +972,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                     }
                     VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
                         vptr_offset += count_own_vtable_entries(tcx, trait_ref);
-                        if trait_ref == upcast_trait_ref {
+                        if trait_ref == upcast_principal {
                             if emit_vptr {
                                 return ControlFlow::Break(Some(vptr_offset));
                             } else {
@@ -969,7 +990,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
         };
 
         let vtable_vptr_slot =
-            prepare_vtable_segments(tcx, source_trait_ref, vtable_segment_callback).unwrap();
+            prepare_vtable_segments(tcx, source_principal, vtable_segment_callback).unwrap();
 
         Ok(ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { vtable_vptr_slot }, nested))
     }
diff --git a/tests/ui/traits/trait-upcasting/fewer-associated.rs b/tests/ui/traits/trait-upcasting/fewer-associated.rs
new file mode 100644
index 00000000000..8228eea2681
--- /dev/null
+++ b/tests/ui/traits/trait-upcasting/fewer-associated.rs
@@ -0,0 +1,25 @@
+// check-pass
+// issue: 114035
+// revisions: current next
+//[next] compile-flags: -Ztrait-solver=next
+
+#![feature(trait_upcasting)]
+
+trait A: B {
+    type Assoc;
+}
+
+trait B {}
+
+fn upcast(a: &dyn A<Assoc = i32>) -> &dyn B {
+    a
+}
+
+// Make sure that we can drop the existential projection `A::Assoc = i32`
+// when upcasting `dyn A<Assoc = i32>` to `dyn B`. Before, we used some
+// complicated algorithm which required rebuilding a new object type with
+// different bounds in order to test that an upcast was valid, but this
+// didn't allow upcasting to t that have fewer associated types
+// than the source type.
+
+fn main() {}
diff --git a/tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.current.stderr b/tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.current.stderr
new file mode 100644
index 00000000000..59c9d573705
--- /dev/null
+++ b/tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.current.stderr
@@ -0,0 +1,14 @@
+error[E0308]: mismatched types
+  --> $DIR/illegal-upcast-from-impl.rs:16:66
+   |
+LL | fn illegal(x: &dyn Sub<Assoc = ()>) -> &dyn Super<Assoc = i32> { x }
+   |                                        -----------------------   ^ expected trait `Super`, found trait `Sub`
+   |                                        |
+   |                                        expected `&dyn Super<Assoc = i32>` because of return type
+   |
+   = note: expected reference `&dyn Super<Assoc = i32>`
+              found reference `&dyn Sub<Assoc = ()>`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.next.stderr b/tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.next.stderr
new file mode 100644
index 00000000000..59c9d573705
--- /dev/null
+++ b/tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.next.stderr
@@ -0,0 +1,14 @@
+error[E0308]: mismatched types
+  --> $DIR/illegal-upcast-from-impl.rs:16:66
+   |
+LL | fn illegal(x: &dyn Sub<Assoc = ()>) -> &dyn Super<Assoc = i32> { x }
+   |                                        -----------------------   ^ expected trait `Super`, found trait `Sub`
+   |                                        |
+   |                                        expected `&dyn Super<Assoc = i32>` because of return type
+   |
+   = note: expected reference `&dyn Super<Assoc = i32>`
+              found reference `&dyn Sub<Assoc = ()>`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.rs b/tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.rs
new file mode 100644
index 00000000000..774474281ea
--- /dev/null
+++ b/tests/ui/traits/trait-upcasting/illegal-upcast-from-impl.rs
@@ -0,0 +1,23 @@
+// revisions: current next
+//[next] compile-flags: -Ztrait-solver=next
+
+#![feature(trait_upcasting)]
+
+trait Super {
+    type Assoc;
+}
+
+trait Sub: Super {}
+
+impl<T: ?Sized> Super for T {
+    type Assoc = i32;
+}
+
+fn illegal(x: &dyn Sub<Assoc = ()>) -> &dyn Super<Assoc = i32> { x }
+//~^ ERROR mismatched types
+
+// Want to make sure that we can't "upcast" to a supertrait that has a different
+// associated type that is instead provided by a blanket impl (and doesn't come
+// from the object bounds).
+
+fn main() {}