Skip to content

Commit 09eeb2b

Browse files
committed
Auto merge of #157710 - theemathas:no-dup-bound-in-dyn, r=<try>
Prohibit conflicting bounds in `dyn`, even with different generics.
2 parents 485ec3f + 67244f1 commit 09eeb2b

19 files changed

Lines changed: 365 additions & 125 deletions

compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use std::assert_matches;
2+
13
use rustc_ast::TraitObjectSyntax;
2-
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
4+
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
35
use rustc_errors::codes::*;
46
use rustc_errors::{
57
Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, StashKey,
@@ -11,8 +13,8 @@ use rustc_hir::{self as hir, HirId, LangItem};
1113
use rustc_lint_defs::builtin::{BARE_TRAIT_OBJECTS, UNUSED_ASSOCIATED_TYPE_BOUNDS};
1214
use rustc_middle::ty::elaborate::ClauseWithSupertraitSpan;
1315
use rustc_middle::ty::{
14-
self, BottomUpFolder, ExistentialPredicateStableCmpExt as _, Ty, TyCtxt, TypeFoldable,
15-
TypeVisitableExt, Upcast,
16+
self, AliasTermKind, BottomUpFolder, ExistentialPredicateStableCmpExt as _, Ty, TyCtxt,
17+
TypeFoldable, TypeVisitableExt, Upcast,
1618
};
1719
use rustc_span::edit_distance::find_best_match_for_name;
1820
use rustc_span::{ErrorGuaranteed, Span};
@@ -69,7 +71,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
6971
dummy_self,
7072
&mut user_written_bounds,
7173
PredicateFilter::SelfOnly,
72-
OverlappingAsssocItemConstraints::Forbidden,
74+
OverlappingAsssocItemConstraints::Allowed,
7375
);
7476
if let Err(GenericArgCountMismatch { invalid_args, .. }) = result.correct {
7577
potential_assoc_items.extend(invalid_args);
@@ -87,6 +89,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
8789
span,
8890
);
8991

92+
// Note: This only includes elaboration due to trait aliases.
93+
// It does not include elaboration due to supertraits.
9094
let (mut elaborated_trait_bounds, elaborated_projection_bounds) =
9195
traits::expand_trait_aliases(tcx, user_written_bounds.iter().copied());
9296

@@ -177,7 +181,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
177181
);
178182
if let Some((old_proj, old_proj_span)) =
179183
projection_bounds.insert(key, (proj, proj_span))
180-
&& tcx.anonymize_bound_vars(proj) != tcx.anonymize_bound_vars(old_proj)
184+
&& proj != old_proj
181185
{
182186
let kind = tcx.def_descr(item_def_id);
183187
let name = tcx.item_name(item_def_id);
@@ -204,6 +208,18 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
204208
// We achieve a stable ordering by walking over the unsubstituted principal trait ref.
205209
let mut ordered_associated_items = vec![];
206210

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

289338
// Flag assoc item bindings that didn't really need to be specified.
290339
for &(projection_bound, span) in projection_bounds.values() {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// We previously accepted conflicting associated type bounds with different generics,
2+
// which resulted in some weirdness.
3+
// See https://github.com/rust-lang/rust/issues/154662
4+
5+
trait Dummy {
6+
type DummyAssoc1;
7+
type DummyAssoc2;
8+
}
9+
struct DummyStruct;
10+
impl Dummy for DummyStruct {
11+
type DummyAssoc1 = i16;
12+
type DummyAssoc2 = i16;
13+
}
14+
15+
trait Super<T> {
16+
type Assoc;
17+
}
18+
19+
trait Sub<D: Dummy>: Super<D::DummyAssoc1, Assoc = i32> + Super<D::DummyAssoc2, Assoc = i64> {}
20+
21+
fn require_trait<D: Dummy, U: Super<D::DummyAssoc1> + ?Sized>() {}
22+
23+
fn use_dyn<D: Dummy>() {
24+
require_trait::<D, dyn Sub<D>>();
25+
//^ ERROR conflicting associated type bindings for `Assoc`
26+
}
27+
28+
fn main() {
29+
// This ends up proving that `dyn Sub<DummyStruct>` implements `Super<i16>`.
30+
// However, `dyn Sub<DummyStruct>` has bounds for both `Assoc = i32` and `Assoc = i64`,
31+
// which is nonsense.
32+
use_dyn::<DummyStruct>();
33+
}
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: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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// We previously accepted conflicting associated type bounds with different generics,
2+
// which resulted in unsoundness.
3+
// See https://github.com/rust-lang/rust/issues/154662
4+
5+
type Payload = Box<i32>;
6+
type Src<'a> = &'a Payload;
7+
type Dst = &'static Payload;
8+
9+
trait Super<T> {
10+
type Assoc;
11+
}
12+
13+
trait Sub<'a, A1, A2>: Super<A1, Assoc = Src<'a>> + Super<A2, Assoc = Dst> {}
14+
15+
trait Callback<A1, A2> {
16+
fn callback<U: Super<A1> + Super<A2> + ?Sized>(
17+
payload: <U as Super<A1>>::Assoc,
18+
) -> <U as Super<A2>>::Assoc;
19+
}
20+
struct CallbackStruct;
21+
impl Callback<i16, i16> for CallbackStruct {
22+
fn callback<U: Super<i16> + ?Sized>(payload: U::Assoc) -> U::Assoc {
23+
payload
24+
}
25+
}
26+
27+
fn require_trait<
28+
'a,
29+
A1,
30+
A2,
31+
U: Super<A1, Assoc = Src<'a>> + Super<A2, Assoc = Dst> + ?Sized,
32+
C: Callback<A1, A2>,
33+
>(
34+
payload: Src<'a>,
35+
) -> Dst {
36+
C::callback::<U>(payload)
37+
}
38+
39+
fn use_dyn<'a, A1, A2, C: Callback<A1, A2>>(payload: Src<'a>) -> Dst {
40+
require_trait::<'a, A1, A2, dyn Sub<'a, A1, A2>, C>(payload)
41+
//^ ERROR conflicting associated type bindings for `Assoc`
42+
}
43+
44+
fn extend<'a>(payload: Src<'a>) -> Dst {
45+
// `dyn Sub<'a, i16, i16>` has both an `Assoc = Src<'a>` bound and an `Assoc = Dst` bound.
46+
use_dyn::<i16, i16, CallbackStruct>(payload)
47+
}
48+
49+
fn main() {
50+
let payload: Box<Payload> = Box::new(Box::new(1));
51+
let wrong: &'static Payload = extend(&*payload);
52+
drop(payload);
53+
println!("{wrong}");
54+
}
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)