Skip to content

Fix replacing supertrait aliases in ReplaceProjectionWith #139774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 17, 2025

Conversation

compiler-errors
Copy link
Member

@compiler-errors compiler-errors commented Apr 14, 2025

The new solver has a procedure called predicates_for_object_candidate, which elaborates the super-bounds and item-bounds that are required to hold for a dyn trait to implement something via a built-in object impl.

In that procedure, there is a folder called ReplaceProjectionWith which is responsible for replacing projections that reference Self, so that we don't encounter cycles when we then go on to normalize those projections in the process of proving these super-bounds.

That folder had a few problems: Firstly, it wasn't actually checking that this was a super bound originating from Self. Secondly, it only accounted for a single projection type def id, but trait objects can have multiple (i.e. trait Foo<A, B>: Bar<A, Assoc = A> + Bar<B, Assoc = B>).

To fix the first, it's simple enough to just add an equality check for the self ty. To fix the second, I implemented a matching step that's very similar to the projection_may_match check we have for upcasting, since on top of having multiple choices, we need to deal with both non-structural matches and ambiguity.

This probably lacks a bit of documentation, but I think it works pretty well.

Fixes rust-lang/trait-system-refactor-initiative#171

r? lcnr

@compiler-errors compiler-errors marked this pull request as ready for review April 14, 2025 00:39
@rustbot
Copy link
Collaborator

rustbot commented Apr 14, 2025

Some changes occurred to the core trait solver

cc @rust-lang/initiative-trait-system-refactor

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) labels Apr 14, 2025
nested: Vec<Goal<I, I::Predicate>>,
is_ambiguous: bool,
Copy link
Member Author

Choose a reason for hiding this comment

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

Ideally we'd use a FallibleTypeFolder here rather than tracking ambiguity on the side. Unfortunately, I think it's impossible to implement a FallibleTypeFolder b/c of the blanket implementation:

impl FallibleTypeFolder<I> for F where F: TypeFolder<I> {}

...causing us to have impl overlap due to downstream impl ambiguity in coherence.

Copy link
Contributor

Choose a reason for hiding this comment

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

:skull_emoji:

Copy link
Contributor

Choose a reason for hiding this comment

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

would like to block this on #139768 then

@lcnr
Copy link
Contributor

lcnr commented Apr 14, 2025

Want to familiarize myself with why exactly this folder is necessary again. A quick look at the comments and the link to lcnr/solver-woes#9 / rust-lang/trait-system-refactor-initiative#6 were not too unhelpful at getting this back into cache.

@compiler-errors
Copy link
Member Author

compiler-errors commented Apr 14, 2025

Yeah, the documentation is lacking in the compiler.

It mentions the need for this folder to prevent unsoundness due to aliases trivially proving their own item bounds, but it doesn't mention that this folder is also required to prevent trivial (in the future -- coinductive, I think) cycles due to the fact that we require alias bounds to hold in the built-in impl.

This is made further interesting by the fact that aliases are allowed to be present in item bounds, for example see:

trait Foo {
    type Assoc: Baz<Self::Assoc>;
}

trait Baz<T> {}
impl Baz<()> for () {}

fn is_foo<T: Foo + ?Sized>() {}

fn main() {
    is_foo::<dyn Foo<Assoc = ()>>();
}

Such a built-in impl conceptually expands to something like:

impl<A> Foo for dyn Foo<Assoc = A>
where
    <Self as Foo>::Assoc: Baz<<Self as Foo>::Assoc>
{
    type Assoc = A;
}

Which I hope is clear that it'll lead to cycles :)

@compiler-errors
Copy link
Member Author

Yeah, the documentation is lacking in the compiler.

Actually, the documentation already mentions a very similar example to the one I gave above:

/// Assemble a list of predicates that would be present on a theoretical
/// user impl for an object type. These predicates must be checked any time
/// we assemble a built-in object candidate for an object type, since they
/// are not implied by the well-formedness of the type.
///
/// For example, given the following traits:
///
/// ```rust,ignore (theoretical code)
/// trait Foo: Baz {
///     type Bar: Copy;
/// }
///
/// trait Baz {}
/// ```
///
/// For the dyn type `dyn Foo<Item = Ty>`, we can imagine there being a
/// pair of theoretical impls:
///
/// ```rust,ignore (theoretical code)
/// impl Foo for dyn Foo<Item = Ty>
/// where
///     Self: Baz,
///     <Self as Foo>::Bar: Copy,
/// {
///     type Bar = Ty;
/// }
///
/// impl Baz for dyn Foo<Item = Ty> {}
/// ```
///
/// However, in order to make such impls non-cyclical, we need to do an
/// additional step of eagerly folding the associated types in the where
/// clauses of the impl. In this example, that means replacing
/// `<Self as Foo>::Bar` with `Ty` in the first impl.

I kinda feel like that is sufficient to motivate the existence of the folder, and I'll just remove the FIXME since it's not necessary anymore.

@lcnr
Copy link
Contributor

lcnr commented Apr 17, 2025

gamer

@bors r+ rollup

@bors
Copy link
Collaborator

bors commented Apr 17, 2025

📌 Commit e1936d2 has been approved by lcnr

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 17, 2025
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Apr 17, 2025
…r=lcnr

Fix replacing supertrait aliases in `ReplaceProjectionWith`

The new solver has a procedure called `predicates_for_object_candidate`, which elaborates the super-bounds and item-bounds that are required to hold for a dyn trait to implement something via a built-in object impl.

In that procedure, there is a folder called `ReplaceProjectionWith` which is responsible for replacing projections that reference `Self`, so that we don't encounter cycles when we then go on to normalize those projections in the process of proving these super-bounds.

That folder had a few problems: Firstly, it wasn't actually checking that this was a super bound originating from `Self`. Secondly, it only accounted for a *single* projection type def id, but trait objects can have multiple (i.e. `trait Foo<A, B>: Bar<A, Assoc = A> + Bar<B, Assoc = B>`).

To fix the first, it's simple enough to just add an equality check for the self ty. To fix the second, I implemented a matching step that's very similar to the `projection_may_match` check we have for upcasting, since on top of having multiple choices, we need to deal with both non-structural matches and ambiguity.

This probably lacks a bit of documentation, but I think it works pretty well.

Fixes rust-lang/trait-system-refactor-initiative#171

r? lcnr
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Apr 17, 2025
…r=lcnr

Fix replacing supertrait aliases in `ReplaceProjectionWith`

The new solver has a procedure called `predicates_for_object_candidate`, which elaborates the super-bounds and item-bounds that are required to hold for a dyn trait to implement something via a built-in object impl.

In that procedure, there is a folder called `ReplaceProjectionWith` which is responsible for replacing projections that reference `Self`, so that we don't encounter cycles when we then go on to normalize those projections in the process of proving these super-bounds.

That folder had a few problems: Firstly, it wasn't actually checking that this was a super bound originating from `Self`. Secondly, it only accounted for a *single* projection type def id, but trait objects can have multiple (i.e. `trait Foo<A, B>: Bar<A, Assoc = A> + Bar<B, Assoc = B>`).

To fix the first, it's simple enough to just add an equality check for the self ty. To fix the second, I implemented a matching step that's very similar to the `projection_may_match` check we have for upcasting, since on top of having multiple choices, we need to deal with both non-structural matches and ambiguity.

This probably lacks a bit of documentation, but I think it works pretty well.

Fixes rust-lang/trait-system-refactor-initiative#171

r? lcnr
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 17, 2025
…iaskrgr

Rollup of 9 pull requests

Successful merges:

 - rust-lang#139774 (Fix replacing supertrait aliases in `ReplaceProjectionWith`)
 - rust-lang#139850 (Hide unstable print kinds within emit_unknown_print_request_help in stable channel)
 - rust-lang#139870 (add retries to remove and create dir all)
 - rust-lang#139902 (do not emit `OpaqueCast` projections with `-Znext-solver`)
 - rust-lang#139931 (bootstrap: enable zlib for LLVM for Windows GNU)
 - rust-lang#139935 (Upgrade to `rustc-rayon-core` 0.5.1)
 - rust-lang#139943 (rustdoc: Support inlined cross-crate re-exported trait aliases)
 - rust-lang#139961 (Two `rustc_const_eval` cleanups)
 - rust-lang#139962 (opt-dist: add a flag for running tests)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit da43826 into rust-lang:master Apr 17, 2025
6 checks passed
@rustbot rustbot added this to the 1.88.0 milestone Apr 17, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Apr 17, 2025
Rollup merge of rust-lang#139774 - compiler-errors:supertrait-alias, r=lcnr

Fix replacing supertrait aliases in `ReplaceProjectionWith`

The new solver has a procedure called `predicates_for_object_candidate`, which elaborates the super-bounds and item-bounds that are required to hold for a dyn trait to implement something via a built-in object impl.

In that procedure, there is a folder called `ReplaceProjectionWith` which is responsible for replacing projections that reference `Self`, so that we don't encounter cycles when we then go on to normalize those projections in the process of proving these super-bounds.

That folder had a few problems: Firstly, it wasn't actually checking that this was a super bound originating from `Self`. Secondly, it only accounted for a *single* projection type def id, but trait objects can have multiple (i.e. `trait Foo<A, B>: Bar<A, Assoc = A> + Bar<B, Assoc = B>`).

To fix the first, it's simple enough to just add an equality check for the self ty. To fix the second, I implemented a matching step that's very similar to the `projection_may_match` check we have for upcasting, since on top of having multiple choices, we need to deal with both non-structural matches and ambiguity.

This probably lacks a bit of documentation, but I think it works pretty well.

Fixes rust-lang/trait-system-refactor-initiative#171

r? lcnr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

builtin object candidates: ReplaceProjectionWith relate failures
4 participants