Skip to content

Commit 44663e0

Browse files
committed
Prohibit conflicting bounds in dyn, even with different generics.
Fixes #154662 In a `dyn` type, if multiple bounds are specified 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, we prohibit multiple bounds for the same associated item, even if the trait's generics are different. As a side effect of this change, we also now allow duplicated bounds in a `dyn` type, as long as the bounds are syntactically identical. The now-unused `OverlappingAsssocItemConstraints` will be removed in a subsequent commit.
1 parent 858ab4a commit 44663e0

21 files changed

Lines changed: 295 additions & 133 deletions

compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rustc_ast::TraitObjectSyntax;
2-
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
2+
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
33
use rustc_errors::codes::*;
44
use rustc_errors::{
55
Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, StashKey,
@@ -11,8 +11,8 @@ use rustc_hir::{self as hir, HirId, LangItem};
1111
use rustc_lint_defs::builtin::{BARE_TRAIT_OBJECTS, UNUSED_ASSOCIATED_TYPE_BOUNDS};
1212
use rustc_middle::ty::elaborate::ClauseWithSupertraitSpan;
1313
use rustc_middle::ty::{
14-
self, BottomUpFolder, ExistentialPredicateStableCmpExt as _, Ty, TyCtxt, TypeFoldable,
15-
TypeVisitableExt, Upcast,
14+
self, AliasTermKind, BottomUpFolder, ExistentialPredicateStableCmpExt as _, Ty, TyCtxt,
15+
TypeFoldable, TypeVisitableExt, Upcast,
1616
};
1717
use rustc_span::edit_distance::find_best_match_for_name;
1818
use rustc_span::{ErrorGuaranteed, Span};
@@ -69,7 +69,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
6969
dummy_self,
7070
&mut user_written_bounds,
7171
PredicateFilter::SelfOnly,
72-
OverlappingAsssocItemConstraints::Forbidden,
72+
OverlappingAsssocItemConstraints::Allowed,
7373
);
7474
if let Err(GenericArgCountMismatch { invalid_args, .. }) = result.correct {
7575
potential_assoc_items.extend(invalid_args);
@@ -87,6 +87,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
8787
span,
8888
);
8989

90+
// Note: This only includes elaboration due to trait aliases.
91+
// It does not include elaboration due to supertraits.
9092
let (mut elaborated_trait_bounds, elaborated_projection_bounds) =
9193
traits::expand_trait_aliases(tcx, user_written_bounds.iter().copied());
9294

@@ -177,7 +179,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
177179
);
178180
if let Some((old_proj, old_proj_span)) =
179181
projection_bounds.insert(key, (proj, proj_span))
180-
&& tcx.anonymize_bound_vars(proj) != tcx.anonymize_bound_vars(old_proj)
182+
&& proj != old_proj
181183
{
182184
let kind = tcx.def_descr(item_def_id);
183185
let name = tcx.item_name(item_def_id);
@@ -204,6 +206,18 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
204206
// We achieve a stable ordering by walking over the unsubstituted principal trait ref.
205207
let mut ordered_associated_items = vec![];
206208

209+
// Detect conflicting bounds from expanding supertraits.
210+
//
211+
// We need to prohibit conflicting bounds from the same item_def_id,
212+
// even if they have different generics. This is because those generics might
213+
// end up being instantiated with the same concrete type, causing unsoundness.
214+
// See https://github.com/rust-lang/rust/issues/154662.
215+
//
216+
// This check could be more lenient, allowing conflicting bounds on different
217+
// generics that we know for sure cannot be instantiated into identical concrete
218+
// types. But for now, we're being conservative.
219+
let mut seen_projection_bounds_ignoring_generics = FxHashMap::default();
220+
207221
if let Some((principal_trait, ref spans)) = principal_trait {
208222
let principal_trait = principal_trait.map_bound(|trait_pred| {
209223
assert_eq!(trait_pred.polarity, ty::PredicatePolarity::Positive);
@@ -240,6 +254,39 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
240254
);
241255
}
242256
ty::ClauseKind::Projection(pred) => {
257+
// See the comment above `let seen_projection_bounds_ignoring_generics`
258+
let kind = pred.projection_term.kind;
259+
let (AliasTermKind::ProjectionTy { def_id }
260+
| AliasTermKind::ProjectionConst { def_id }) = kind
261+
else {
262+
panic!(
263+
"Unexpected projection kind in lower_trait_object_ty: `{kind:?}`"
264+
)
265+
};
266+
let term = pred.term;
267+
// This clause specifies that the `kind` is equal to `term`.
268+
// We record this, and check for duplicates.
269+
if let Some(old_term) =
270+
seen_projection_bounds_ignoring_generics.insert(kind, term)
271+
&& old_term != term
272+
{
273+
let name = tcx.item_name(def_id);
274+
self.dcx()
275+
.struct_span_err(
276+
span,
277+
format!(
278+
"conflicting {} bindings for `{}`",
279+
kind.descr(),
280+
name,
281+
),
282+
)
283+
// FIXME: Improve diagnostics by pointing to
284+
// where the bound is specified.
285+
.with_note(format!("`{name}` is specified to be `{old_term}`"))
286+
.with_note(format!("`{name}` is also specified to be `{term}`"))
287+
.emit();
288+
}
289+
243290
let pred = bound_predicate.rebind(pred);
244291
// A `Self` within the original bound will be instantiated with a
245292
// `trait_object_dummy_self`, so check for that.
@@ -285,6 +332,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
285332
}
286333
}
287334
}
335+
drop(seen_projection_bounds_ignoring_generics);
288336

289337
// Flag assoc item bindings that didn't really need to be specified.
290338
for &(projection_bound, span) in projection_bounds.values() {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
//@build-pass
2-
// We currently accept conflicting associated type bounds with different generics,
3-
// which results in some weirdness.
1+
// We previously accepted conflicting associated type bounds with different generics,
2+
// which resulted in some weirdness.
43
// See https://github.com/rust-lang/rust/issues/154662
54

65
trait Dummy {
@@ -23,6 +22,7 @@ fn require_trait<D: Dummy, U: Super<D::DummyAssoc1> + ?Sized>() {}
2322

2423
fn use_dyn<D: Dummy>() {
2524
require_trait::<D, dyn Sub<D>>();
25+
//~^ ERROR conflicting associated type bindings for `Assoc`
2626
}
2727

2828
fn main() {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: conflicting associated type bindings for `Assoc`
2+
--> $DIR/conflicting-bounds-different-generics-complex.rs:24:24
3+
|
4+
LL | require_trait::<D, dyn Sub<D>>();
5+
| ^^^^^^^^^^
6+
|
7+
= note: `Assoc` is specified to be `i64`
8+
= note: `Assoc` is also specified to be `i32`
9+
10+
error: aborting due to 1 previous error
11+
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
//@build-pass
2-
31
trait Super<T> {
42
type Assoc;
53
}
64

75
trait Sub: Super<i32, Assoc = u32> + Super<i64, Assoc = u64> {}
86

97
fn foo(_: &dyn Sub) {}
8+
//~^ ERROR conflicting associated type bindings for `Assoc`
109

1110
fn main() {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: conflicting associated type bindings for `Assoc`
2+
--> $DIR/conflicting-bounds-different-generics-simple.rs:7:12
3+
|
4+
LL | fn foo(_: &dyn Sub) {}
5+
| ^^^^^^^
6+
|
7+
= note: `Assoc` is specified to be `u64`
8+
= note: `Assoc` is also specified to be `u32`
9+
10+
error: aborting due to 1 previous error
11+

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
//@build-pass
2-
// We currently accept conflicting associated type bounds with different generics,
3-
// which results in unsoundness.
1+
// We previously accepted conflicting associated type bounds with different generics,
2+
// which resulted in unsoundness.
43
// See https://github.com/rust-lang/rust/issues/154662
54

65
type Payload = Box<i32>;
@@ -39,6 +38,7 @@ fn require_trait<
3938

4039
fn use_dyn<'a, A1, A2, C: Callback<A1, A2>>(payload: Src<'a>) -> Dst {
4140
require_trait::<'a, A1, A2, dyn Sub<'a, A1, A2>, C>(payload)
41+
//~^ ERROR conflicting associated type bindings for `Assoc`
4242
}
4343

4444
fn extend<'a>(payload: Src<'a>) -> Dst {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: conflicting associated type bindings for `Assoc`
2+
--> $DIR/conflicting-bounds-different-generics-unsound.rs:40:33
3+
|
4+
LL | require_trait::<'a, A1, A2, dyn Sub<'a, A1, A2>, C>(payload)
5+
| ^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `Assoc` is specified to be `&'static Box<i32>`
8+
= note: `Assoc` is also specified to be `&'a Box<i32>`
9+
10+
error: aborting due to 1 previous error
11+

tests/ui/associated-type-bounds/duplicate-bound-err.rs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
//@ edition: 2024
22

3-
#![feature(
4-
min_generic_const_args,
5-
type_alias_impl_trait,
6-
return_type_notation
7-
)]
3+
#![feature(min_generic_const_args, type_alias_impl_trait, return_type_notation)]
84
#![expect(incomplete_features)]
95
#![allow(refining_impl_trait_internal)]
106

@@ -77,27 +73,21 @@ fn uncallable(_: impl Iterator<Item = i32, Item = u32>) {}
7773

7874
fn uncallable_const(_: impl Trait<ASSOC = 3, ASSOC = 4>) {}
7975

80-
fn uncallable_rtn(
81-
_: impl Trait<foo(..): Trait<ASSOC = 3>, foo(..): Trait<ASSOC = 4>>
82-
) {}
76+
fn uncallable_rtn(_: impl Trait<foo(..): Trait<ASSOC = 3>, foo(..): Trait<ASSOC = 4>>) {}
8377

8478
type MustFail = dyn Iterator<Item = i32, Item = u32>;
85-
//~^ ERROR [E0719]
86-
//~| ERROR conflicting associated type bindings
79+
//~^ ERROR conflicting associated type bindings
8780

8881
trait Trait2 {
8982
type const ASSOC: u32;
9083
}
9184

9285
type MustFail2 = dyn Trait2<ASSOC = 3u32, ASSOC = 4u32>;
93-
//~^ ERROR [E0719]
94-
//~| ERROR conflicting associated constant bindings
86+
//~^ ERROR conflicting associated constant bindings
9587

96-
type MustFail3 = dyn Iterator<Item = i32, Item = i32>;
97-
//~^ ERROR [E0719]
88+
type Allowed = dyn Iterator<Item = i32, Item = i32>;
9889

99-
type MustFail4 = dyn Trait2<ASSOC = 3u32, ASSOC = 3u32>;
100-
//~^ ERROR [E0719]
90+
type Allowed2 = dyn Trait2<ASSOC = 3u32, ASSOC = 3u32>;
10191

10292
trait Trait3 {
10393
fn foo() -> impl Iterator<Item = i32, Item = u32>;

0 commit comments

Comments
 (0)