Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -749,14 +749,19 @@ fn receiver_is_dispatchable<'tcx>(
let trait_predicate = ty::TraitRef::new_from_args(tcx, trait_def_id, args);
clauses.push(trait_predicate.upcast(tcx));

// U satisfies `Trait`'s where-bounds.
// U satisfies Trait's trait bounds. When normalizing `U: Trait<Arg1, ..., ArgN>`, aliases
// in the arguments of supertrait bounds can generate trait obligations which may only be
// be provable using where-clauses from Trait (#161621).
clauses.extend(
tcx.clauses_of(trait_def_id)
.instantiate(tcx, args)
.clauses
.into_iter()
.map(Unnormalized::skip_norm_wip),
.map(Unnormalized::skip_norm_wip)
.filter(|clause| matches!(clause.kind().skip_binder(), ty::ClauseKind::Trait(_))),
);
// FIXME(@dianne): we need projection clauses too in some form, but adding them all
// unmodified can introduce ambiguity (#163550)

let meta_sized_predicate = {
let meta_sized_did = tcx.require_lang_item(LangItem::MetaSized, DUMMY_SP);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! Tests for specific instances of <https://github.com/rust-lang/rust/issues/161621> that don't
//! pass yet. See `dispatchability-placeholder-satisfies-bounds.rs` for context.
// FIXME(@dianne): this should be gone soon
//@ known-bug: unknown

@dianne dianne Oct 7, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left this as unknown for now since the issue is currently marked as closed and this issue is significantly more specific than what was reported there. not sure if it should be reopened until the followup to this lands or a new issue is warranted

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you're following up immediately, this is fine


trait HasAssoc {
type Assoc;
}

trait Parent<T> {}

trait NotDynCompatible
where
// We need to be able to normalize this for RustaceansAreAwesome in order to prove
// `<(RustaceansAreAwesome,) as HasAssoc>::Assoc: Owned` for the projection in the argument to
// the `Self: Parent<...>` supertrait bound. Currently a projection clause for it is missing
// from the `ParamEnv`.
(Self,): HasAssoc<Assoc = str>,
Self: Parent<<<(Self,) as HasAssoc>::Assoc as ToOwned>::Owned>,
{
fn f(&self);
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error[E0277]: the trait bound `<(RustaceansAreAwesome,) as HasAssoc>::Assoc: Clone` is not satisfied
--> $DIR/dispatchability-placeholder-does-not-satisfy-bounds.rs:21:5
|
LL | fn f(&self);
| ^^^^^^^^^^^^ the trait `Clone` is not implemented for `<(RustaceansAreAwesome,) as HasAssoc>::Assoc`
|
= note: required for `<(RustaceansAreAwesome,) as HasAssoc>::Assoc` to implement `ToOwned`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.

@dianne dianne Oct 7, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've dumped all the passing tests in this file since they share a significant amount of context, especially when adding in the tests I've written for the followup to this, where many tests build on concepts that were set up in previous tests. maybe some of them should be split apart though?

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fine this way. A little bit more annoying to debug when only one of them breaks, but they are all very related

Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
//! Regression tests for <https://github.com/rust-lang/rust/issues/161621>. When checking whether
//! method receivers for a trait `Trait` are dynamically dispatchable, we use a placeholder type in
//! place of `dyn Trait` to avoid a cycle (as that would require determining whether `Trait` is dyn-
//! compatible). This placeholder must satisfy `Trait`'s where-bounds to avoid errors in param-
//! environment normalization.
//! Regression tests for <https://github.com/rust-lang/rust/issues/161621> and similar. When
//! checking whether method receivers for a trait `Trait` are dynamically dispatchable, we (at the
//! time of writing) use a placeholder type called `RustaceansAreAwesome` in place of `dyn Trait` to
//! avoid a cycle (as using `dyn Trait` requires determining whether `Trait` is dyn-compatible). We
//! assume that `RustaceansAreAwesome` implements `Trait`, so when normalizing the param-environment
//! we construct, we may have to prove obligations for aliases encountered in arguments to super-
//! trait bounds. It's possible for these to only be provable using clauses from `Trait` or its
//! supertraits that would be necessary for `RustaceansAreAwesome` to implement `Trait`.
//@ check-pass

// Test 1
// This works for `Self: Super<...Ty::<RustaceansAreAwesome>::Assoc...>` after the `where`.
// We need to assume `(Args, RustaceansAreAwesome::Output): SignatureToFnPtr`.

use std::ops::Deref;

Expand All @@ -22,7 +26,8 @@ where
fn method(&self);
}

// Test 2
// This works for `Trait: Super<...Ty<RustaceansAreAwesome>::Assoc...>` before the `where`.
// We need to assume `A: Associator<RustaceansAreAwesome>`.

trait Associator<S: ?Sized> {
type Point;
Expand All @@ -34,4 +39,60 @@ trait Trait<A: Associator<Self> + ?Sized>: Marker<A::Point> {
fn method(&self);
}

// Regression test for <https://github.com/rust-lang/rust/issues/161621>: we need to be careful with
// projection clauses involving `RustaceansAreAwesome`. Here, if we simply assumed that
// `<T as Matrix2D>::RowIndex == <RustaceansAreAwesome as Matrix2D>::RowIndex`, we'd end up with
// ambiguity, as we also assume `<T as Matrix2D>::RowIndex == <Self as Matrix2D>::RowIndex`.`Self`
// stands in for the `Self` type of the impl we're imagining dispatching to (pretending
// `RustaceansAreAwesome` is a trait object).

pub trait Matrix {
type Coordinates;
}

pub trait Matrix2D: Matrix<Coordinates = <Self as Matrix2D>::RowIndex> {
type RowIndex;
}

pub trait Transposable<T: Matrix2D<RowIndex = Self::RowIndex>>: Matrix2D {
fn transpose(&self) -> T;
}

// An example where we can't add any additional projections without introducing ambiguity: we don't
// want to assume `<() as HasAssoc>::Assoc` is both `Self` and `RustaceansAreAwesome`. At the time
// of writing, we let `<() as HasAssoc>::Assoc` normalize to `Self` during receiver dispatchability
// checking. This should be fine, since to call methods on a `dyn TechnicallyDynCompatible`, we'd
// need `<() as HasAssoc>::Assoc` to be `dyn TechnicallyDynCompatible`. This is the same reasoning
// that lets us assume `RustaceansAreAwesome` implements its trait at all: to use the trait object,
// it must implement the trait; otherwise, receiver dispatchability doesn't mean much.

trait HasAssoc {
type Assoc;
}

impl HasAssoc for () {
type Assoc = ();
}

trait Parent<T: ?Sized> {}

trait TechnicallyDynCompatible
where
(): HasAssoc<Assoc = Self>,
Self: Parent<<() as HasAssoc>::Assoc>,
{
fn f(&self) {}
}

// Since we have `TechnincallyDynCompatible` defined, let's keep its dyn-compatibility from breaking
// accidentally (and also sanity-check the above comment).

impl Parent<()> for () {}
impl TechnicallyDynCompatible for () {}

fn technically_dyn_compatible_is_dyn_compatible() {
let x: &dyn TechnicallyDynCompatible = &();
// We can't call `x.f()` since `<() as HasAssoc>::Assoc` isn't `dyn TechnicallyDynCompatible`.
}

fn main() {}
Loading