Skip to content

Commit 97592bc

Browse files
committed
Require coherence of supertrait associated item bounds for dyn-compability
Fixes #154662 In a `dyn` type, if multiple bounds are specified via supertraits for the same associated item, then we previously accepted them if the relevant trait's generics are different, even if the bounds conflict. This was unsound, since those generics could end up being instantiated with identical concrete types, causing the `dyn` type to have two different "values" for the same bound. Thus, if a trait has multiple supertrait bounds for the same associated item, we check whether those bounds are coherent, similarly to how we check for overlap between impls (i.e., we check if the generics could be instantiated to be the same while the "values" of the bounds are different). If the bounds are incoherent, then we consider the trait to be dyn-incompatible.
1 parent 862c83f commit 97592bc

9 files changed

Lines changed: 244 additions & 201 deletions

compiler/rustc_middle/src/traits/mod.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,24 @@ pub enum DynCompatibilityViolation {
782782

783783
/// Generic associated type (GAT).
784784
GenericAssocTy(Symbol, Span),
785+
786+
/// We consider a trait dyn-incompatible if it has supertrait bounds that
787+
/// include two associated type/const bounds on the same associated type/const
788+
/// `DefId`, and have generics that could be instantiated into the same concrete
789+
/// types, but the bounds may have unequal terms.
790+
///
791+
/// Trait objects from such traits could otherwise be instantiated into
792+
/// a concrete type with conflicting associated types, violating coherence,
793+
/// which is unsound. See #154662.
794+
///
795+
/// Checking this predicate is conceptually like checking for
796+
/// the coherence of the builtin impls for `dyn`, to make sure that the
797+
/// associated type/const don't conflict with each other between the impls.
798+
//
799+
// FIXME: Improve diagnostics for this.
800+
// * Tell the user the exact projections involved that are in conflict
801+
// * Point to where the projection bound was written
802+
IncoherentSupertraitAssocs(Symbol, Span),
785803
}
786804

787805
impl DynCompatibilityViolation {
@@ -851,6 +869,10 @@ impl DynCompatibilityViolation {
851869
Self::GenericAssocTy(name, _) => {
852870
format!("it contains generic associated type `{name}`").into()
853871
}
872+
Self::IncoherentSupertraitAssocs(name, _) => {
873+
format!("it has conflicting associated item bounds for {name} in supertraits")
874+
.into()
875+
}
854876
}
855877
}
856878

@@ -860,7 +882,8 @@ impl DynCompatibilityViolation {
860882
| Self::SizedSelf(_)
861883
| Self::SupertraitSelf(_)
862884
| Self::SupertraitNonLifetimeBinder(..)
863-
| Self::SupertraitConst(_) => DynCompatibilityViolationSolution::None,
885+
| Self::SupertraitConst(_)
886+
| Self::IncoherentSupertraitAssocs(..) => DynCompatibilityViolationSolution::None,
864887
Self::Method(
865888
name,
866889
MethodViolation::StaticMethod(Some((add_self_sugg, make_sized_sugg))),
@@ -890,7 +913,8 @@ impl DynCompatibilityViolation {
890913
| Self::SupertraitConst(spans) => spans.clone(),
891914
Self::Method(_, _, span)
892915
| Self::AssocConst(_, _, span)
893-
| Self::GenericAssocTy(_, span) => {
916+
| Self::GenericAssocTy(_, span)
917+
| Self::IncoherentSupertraitAssocs(_, span) => {
894918
if *span != DUMMY_SP {
895919
smallvec![*span]
896920
} else {

compiler/rustc_trait_selection/src/traits/coherence.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ fn impl_intersection_has_negative_obligation(
560560
.any(|(clause, _)| try_prove_negated_where_clause(infcx, clause, param_env))
561561
}
562562

563-
fn plug_infer_with_placeholders<'tcx>(
563+
pub(super) fn plug_infer_with_placeholders<'tcx>(
564564
infcx: &InferCtxt<'tcx>,
565565
universe: ty::UniverseIndex,
566566
value: impl TypeVisitable<TyCtxt<'tcx>>,

compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@
66
77
use std::ops::ControlFlow;
88

9+
use itertools::Itertools;
10+
use rustc_data_structures::fx::FxHashMap;
911
use rustc_errors::FatalError;
1012
use rustc_hir as hir;
1113
use rustc_hir::attrs::lang_items::LangItem;
12-
use rustc_hir::def_id::DefId;
14+
use rustc_hir::def_id::{CRATE_DEF_ID, DefId};
15+
use rustc_infer::infer::BoundRegionConversionTime;
1316
use rustc_middle::query::Providers;
1417
use rustc_middle::ty::{
15-
self, EarlyBinder, GenericArgs, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
16-
TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, Unnormalized,
17-
Upcast, elaborate,
18+
self, Clause, EarlyBinder, GenericArgs, PolyProjectionPredicate, ProjectionPredicate, Ty,
19+
TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable,
20+
TypeVisitableExt, TypeVisitor, TypingMode, Unnormalized, Upcast, elaborate,
1821
};
1922
use rustc_span::{DUMMY_SP, Span};
2023
use smallvec::SmallVec;
@@ -23,9 +26,10 @@ use tracing::{debug, instrument};
2326
use super::elaborate;
2427
use crate::infer::TyCtxtInferExt;
2528
pub use crate::traits::DynCompatibilityViolation;
29+
use crate::traits::coherence::plug_infer_with_placeholders;
2630
use crate::traits::query::evaluate_obligation::InferCtxtExt;
2731
use crate::traits::{
28-
AssocConstViolation, MethodViolation, Obligation, ObligationCause,
32+
AssocConstViolation, MethodViolation, Obligation, ObligationCause, ObligationCtxt,
2933
normalize_param_env_or_error, util,
3034
};
3135

@@ -55,7 +59,8 @@ fn dyn_compatibility_violations(
5559
debug!("dyn_compatibility_violations: {:?}", trait_def_id);
5660
tcx.arena.alloc_from_iter(
5761
elaborate::supertrait_def_ids(tcx, trait_def_id)
58-
.flat_map(|def_id| dyn_compatibility_violations_for_trait(tcx, def_id)),
62+
.flat_map(|def_id| dyn_compatibility_violations_for_trait(tcx, def_id))
63+
.chain(incoherent_supertrait_assocs(tcx, trait_def_id)),
5964
)
6065
}
6166

@@ -979,6 +984,103 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for IllegalRpititVisitor<'tcx> {
979984
}
980985
}
981986

987+
/// Computes [`DynCompatibilityViolation::IncoherentSupertraitAssocs`]
988+
#[instrument(level = "debug", skip(tcx))]
989+
fn incoherent_supertrait_assocs(
990+
tcx: TyCtxt<'_>,
991+
trait_def_id: DefId,
992+
) -> impl Iterator<Item = DynCompatibilityViolation> {
993+
let clauses = tcx
994+
.clauses_of(trait_def_id)
995+
.instantiate_identity(tcx)
996+
.clauses
997+
.into_iter()
998+
.map(Unnormalized::skip_norm_wip);
999+
// Map from associated items to projection predicates that apply to them.
1000+
let mut preds_for_assoc = FxHashMap::<DefId, Vec<PolyProjectionPredicate<'_>>>::default();
1001+
elaborate(tcx, clauses).filter_map(Clause::as_projection_clause).flat_map(move |proj| {
1002+
let prev_projs = preds_for_assoc.entry(proj.item_def_id()).or_default();
1003+
let violations: Vec<_> = prev_projs
1004+
.iter()
1005+
.copied()
1006+
.filter(move |&prev_proj| {
1007+
!does_pair_have_coherent_supertrait_assocs(tcx, trait_def_id, prev_proj, proj)
1008+
})
1009+
.map(move |_| {
1010+
DynCompatibilityViolation::IncoherentSupertraitAssocs(
1011+
tcx.item_name(proj.item_def_id()),
1012+
tcx.def_ident_span(proj.item_def_id())
1013+
.expect("Associated items should have a def_ident_span"),
1014+
)
1015+
})
1016+
.collect();
1017+
prev_projs.push(proj);
1018+
violations
1019+
})
1020+
}
1021+
1022+
#[instrument(level = "debug", skip(tcx), ret)]
1023+
fn does_pair_have_coherent_supertrait_assocs<'tcx>(
1024+
tcx: TyCtxt<'tcx>,
1025+
trait_def_id: DefId,
1026+
proj_1: PolyProjectionPredicate<'tcx>,
1027+
proj_2: PolyProjectionPredicate<'tcx>,
1028+
) -> bool {
1029+
let infcx = tcx
1030+
.infer_ctxt()
1031+
.with_next_trait_solver(tcx.next_trait_solver_in_coherence())
1032+
.build(TypingMode::Coherence);
1033+
1034+
// We instantiate type parameters in the two projections with the same
1035+
// fresh inference variables.
1036+
let trait_args = infcx.fresh_args_for_item(DUMMY_SP, trait_def_id);
1037+
let process_proj = |proj: PolyProjectionPredicate<'tcx>| -> ProjectionPredicate<'tcx> {
1038+
let instantiated_proj = EarlyBinder::bind(tcx, proj).instantiate(tcx, trait_args);
1039+
infcx.instantiate_binder_with_fresh_vars(
1040+
DUMMY_SP,
1041+
BoundRegionConversionTime::AssocTypeProjection(proj.item_def_id()),
1042+
instantiated_proj.skip_norm_wip(),
1043+
)
1044+
};
1045+
let proj_1 = process_proj(proj_1);
1046+
let proj_2 = process_proj(proj_2);
1047+
assert_eq!(
1048+
proj_1.projection_term.kind, proj_2.projection_term.kind,
1049+
"should compare the same projection kind"
1050+
);
1051+
1052+
let ocx = ObligationCtxt::new(&infcx);
1053+
let param_env = tcx.param_env(trait_def_id);
1054+
// Constrain the two projections to be on the same trait, including generics.
1055+
// If this fails, then the two projections do not conflict with each other,
1056+
// as they're projecting different things.
1057+
let can_equate_generics =
1058+
proj_1.projection_term.args.iter().zip_eq(proj_2.projection_term.args).all(
1059+
|(arg_1, arg_2)| ocx.eq(&ObligationCause::dummy(), param_env, arg_1, arg_2).is_ok(),
1060+
);
1061+
// FIXME: Is it sound to return true here if we call .resolve_regions() here,
1062+
// and that produces an error?
1063+
if !can_equate_generics || ocx.try_evaluate_obligations().has_errors() {
1064+
return true;
1065+
}
1066+
// Discard any ambiguous obligations. In doing so, we're conservatively assuming that
1067+
// the two projections might apply to the same trait-with-generics. This is sound, since
1068+
// it can only cause this function to return false when it could have returned true,
1069+
// which at worst can only cause a trait to be marked as dyn-incompatible.
1070+
// FIXME: Is this strictly necessary? Can retaining the ambiguous obligations
1071+
// to the following check cause any problems?
1072+
drop(ocx);
1073+
1074+
// Now that we've constrained the two projections to be on the same thing,
1075+
// we check whether the two terms are necessarily equal to each other.
1076+
// If they are, then the two projections are coherent.
1077+
plug_infer_with_placeholders(&infcx, ty::UniverseIndex::ROOT, (proj_1, proj_2));
1078+
let ocx = ObligationCtxt::new(&infcx);
1079+
ocx.eq(&ObligationCause::dummy(), param_env, proj_1.term, proj_2.term).is_ok()
1080+
&& ocx.evaluate_obligations_error_on_ambiguity().no_errors()
1081+
&& ocx.resolve_regions(CRATE_DEF_ID, param_env, []).is_empty()
1082+
}
1083+
9821084
pub(crate) fn provide(providers: &mut Providers) {
9831085
*providers = Providers {
9841086
dyn_compatibility_violations,

tests/ui/associated-type-bounds/conflicting-bounds-different-generics-simple.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
//@ known-bug: #154662
2-
//@ failure-status: 101
3-
//@ compile-flags: --emit link
4-
51
// We currently accept conflicting associated type bounds with different generics,
62
// which results in an ICE, since those generics can be instantiated with the
73
// same concrete type.
@@ -16,8 +12,10 @@ trait Sub<T, U>: Super<T, Assoc = u32> + Super<U, Assoc = u64> {
1612
}
1713

1814
fn foo<T, U>(x: Option<&dyn Sub<T, U>>) {
15+
//~^ ERROR the trait `Sub` is not dyn compatible
1916
if false {
2017
x.unwrap().method();
18+
//~^ ERROR the trait `Sub` is not dyn compatible
2119
}
2220
}
2321

@@ -26,4 +24,5 @@ fn main() {
2624
// However, `dyn Sub<i16, i16>` has bounds for both `Assoc = u32` and `Assoc = u64`,
2725
// which is nonsense.
2826
foo::<i16, i16>(None);
27+
//~^ ERROR the trait `Sub` is not dyn compatible
2928
}

0 commit comments

Comments
 (0)