diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 433735e827bdd50651f7ce10e19ac613fe608a17..6d5db3336cfbf288046c9c5fda92bffcbcba196a 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -481,3 +481,31 @@ fn to_trace( 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 25077f9bb97ee509b7b8f9d2423cd18de5e95b41..2b420bd869b107e4e457c3b2e249e5d21e2c625a 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 @@ enum Mismatch<'a> { (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 @@ fn values_str( 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 a73e84f94c4ca21c51546446b531fabffae96567..aaabf1482e2d2e322e5b4259ade2d6c02cce7a3e 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>), PolyTraitRefs(ExpectedFound>), Sigs(ExpectedFound>), + ExistentialTraitRef(ExpectedFound>), + ExistentialProjection(ExpectedFound>), } 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 e793f481995f9c12ea3cbcccaeee75dc0b1633d7..4e2af3816ac650dda013ae503240b7a551cedfe4 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 f1e567e9bf86c6c326115e6b40e47b171f521e9b..8759fecb05a2d81587d875c4a5d130680511fa59 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 @@ pub(super) fn format_candidate(&mut self, candidate: &GoalCandidate<'_>) -> std: 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 6e68022031a07972ff0e0b31ce4271fc41a38f92..d3fd49150ba2ac706d5f09505c46b468cf74088f 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -2734,8 +2734,9 @@ pub struct PrintClosureAsImpl<'tcx> { // 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 14a5b9656e50535e32c23823a6f4afd1438e5293..41565fe5dd9635db591cda268650226bc872cbe5 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 @@ fn consider_builtin_unsize_candidates( 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 @@ fn consider_builtin_dyn_upcast_candidates( 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 @@ fn consider_builtin_dyn_upcast_candidates( 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>| { - 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 @@ fn consider_builtin_unsize_to_dyn( 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>, + a_region: ty::Region<'tcx>, + b_data: &'tcx ty::List>, + b_region: ty::Region<'tcx>, + upcast_principal: Option>, + ) -> 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 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 bf09681c66d1ad4370fe16bb4cbce617b657c4f5..77304984402689487ede2a906af1f4b903f7cad2 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 @@ fn confirm_trait_upcasting_unsize_candidate( // `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 + B` + 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 @@ fn confirm_trait_upcasting_unsize_candidate( } 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 @@ fn confirm_trait_upcasting_unsize_candidate( }; 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 0000000000000000000000000000000000000000..8228eea2681aa52f4da537c3f921e99ce7db90e5 --- /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) -> &dyn B { + a +} + +// Make sure that we can drop the existential projection `A::Assoc = i32` +// when upcasting `dyn A` 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 0000000000000000000000000000000000000000..59c9d5737056aa25b48945d93daf2b46a36d6b3e --- /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) -> &dyn Super { x } + | ----------------------- ^ expected trait `Super`, found trait `Sub` + | | + | expected `&dyn Super` because of return type + | + = note: expected reference `&dyn Super` + found reference `&dyn Sub` + +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 0000000000000000000000000000000000000000..59c9d5737056aa25b48945d93daf2b46a36d6b3e --- /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) -> &dyn Super { x } + | ----------------------- ^ expected trait `Super`, found trait `Sub` + | | + | expected `&dyn Super` because of return type + | + = note: expected reference `&dyn Super` + found reference `&dyn Sub` + +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 0000000000000000000000000000000000000000..774474281eab9a48b506b309f1df1fc51cca78a9 --- /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 Super for T { + type Assoc = i32; +} + +fn illegal(x: &dyn Sub) -> &dyn Super { 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() {}