Skip to content

Prevent rustdoc auto-trait ICEs with -Znext-solver=globally#158346

Merged
rust-bors[bot] merged 2 commits into
rust-lang:mainfrom
Jamesbarford:fix/auto-trait-impl
Jul 7, 2026
Merged

Prevent rustdoc auto-trait ICEs with -Znext-solver=globally#158346
rust-bors[bot] merged 2 commits into
rust-lang:mainfrom
Jamesbarford:fix/auto-trait-impl

Conversation

@Jamesbarford

@Jamesbarford Jamesbarford commented Jun 24, 2026

Copy link
Copy Markdown
Contributor
  • Ensure rustdoc with -Znext-solver=globally does not ICE.

r? @lcnr

@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. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. labels Jun 24, 2026
@Jamesbarford Jamesbarford force-pushed the fix/auto-trait-impl branch from 8806252 to 52e368f Compare June 25, 2026 07:38

let (infcx, orig_env) = tcx.infer_ctxt().build_with_typing_env(typing_env);
let mut selcx = SelectionContext::new(&infcx);
for polarity in [ty::PredicatePolarity::Positive, ty::PredicatePolarity::Negative] {

@lcnr lcnr Jun 25, 2026

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.

can you instead copy the behavior of

fn disqualify_auto_trait_candidate_due_to_possible_impl(
here.

rn this doesn't consider there to be an explicit impl for MyType<T> if there's just impl Send for MyType<u32>

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ab62f15 👍

let field_clauses = adt_def
.all_fields()
.map(|field| field.ty(tcx, args).skip_norm_wip())
.filter(|field_ty| field_ty.has_non_region_param())

@lcnr lcnr Jun 25, 2026

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.

Comment on lines +240 to +242
let full_user_env = ty::ParamEnv::new(
tcx.mk_clauses_from_iter(orig_env.caller_bounds().iter().chain(field_clauses)),
);

@lcnr lcnr Jun 25, 2026

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.

hmm? 🤔 what are you trying to do/please provide a comment

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.

generally, can you describe the algorithm you're trying to implement in words?

This is currently somewhat wrong and I would like to know what it is that you are trying to do. Might also be useful to show the intended behavior on one or two examples

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is meant to be deliberately coarse and simply not ICE. Not a faithful port of the old algorithm. If I'm not mistaken the old path minimises the where-clauses by repeatedly selecting and folding unimplemented param-bounds back into the ParamEnv using the old-solver internals which ICEs under the new solver.

The new path just does one rather simple pass:

  1. If there is a user-written impl then ExplicitImpl.
  2. If ty isn't an ADT NoImpl.
  3. Otherwise assume each generic field implements the trait (field_ty: Trait for every field mentioning a type/const param) and add those as the where-clauses.
  4. Verify ty: Trait under those assumptions
    • true error NegativeImpl
    • ambiguity NoImpl
    • clean PositiveImpl

Example:

// foo.rs
pub struct Foo<T> {
    pub data: Box<T>,
}

// previously
impl<T> Send for Foo<T>
  where
      T: Send,

// This PR
impl<T> Send for Foo<T>
  where
      Box<T>: Send,

So I'm emitting the raw field bound rather than minimising to T: Send. But I understand that this is not what we are wanting to do? Instead I should be following fn disqualify_auto_trait_candidate_due_to_possible_impl(...)?

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.

That makes sense 👍

Treating ambiguity as NoImpl should be wrong though 🤔

struct Foo<'a, 'b, T: 'a + 'b>(&'a T, &'b T);

proving Foo<'a, 'b, T>: Send given &'a T: Send and &'b T: Send results in an ambiguity error.

I think what step 4 is trying to handle is types which don't implement auto traits because of one of the non-generic fields 🤔

what we can do here is to instead replace all generic parameters with inference variables and then check whether Foo<'?0, '?1, ?t>: Send holds. If this returns an error, we emit a NegativeImpl. We never emit NoImpl for now

I have some ideas on how to refine this in the future, but I think as an initial impl this is good enough :>

The way to replace generic params with infer vars is e.g.

let generic_args = ty::GenericArgs::for_item(self.tcx, m.def_id, |param, _| {
self.var_for_def(deref.span, param)
});

and then you take the Adt type, wrap it in an EarlyBinder and instantiate it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

b6fdb1d 👍 I found the following helper which does what you say under the hood so used it instead.

/// Given a set of generics defined on a type or impl, returns the generic parameters mapping
/// each type/region parameter to a fresh inference variable.
pub fn fresh_args_for_item(&self, span: Span, def_id: DefId) -> GenericArgsRef<'tcx> {
GenericArgs::for_item(self.tcx, def_id, |param, _| self.var_for_def(span, param))
}

I've also added a couple of tests, not sure if you want them but they were fairly useful for making sure I captured the behaviour you described.

JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jun 25, 2026
…t-bounds, r=fmease

Fix: auto trait, const trait bound

Split from rust-lang#158346. I was fiddling about with the code to understand the behaviour of the snippet below, got confused, and somehow fixed something 🤷

```rust
#![feature(const_trait_impl)]

pub struct Outer<T>(Inner<T>);
struct Inner<T>(T);

impl<T> Unpin for Inner<T>
where
    T: const std::default::Default,
{}
```

Fixes rust-lang#149281
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jun 25, 2026
…t-bounds, r=fmease

Fix: auto trait, const trait bound

Split from rust-lang#158346. I was fiddling about with the code to understand the behaviour of the snippet below, got confused, and somehow fixed something 🤷

```rust
#![feature(const_trait_impl)]

pub struct Outer<T>(Inner<T>);
struct Inner<T>(T);

impl<T> Unpin for Inner<T>
where
    T: const std::default::Default,
{}
```

Fixes rust-lang#149281
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jun 25, 2026
…t-bounds, r=fmease

Fix: auto trait, const trait bound

Split from rust-lang#158346. I was fiddling about with the code to understand the behaviour of the snippet below, got confused, and somehow fixed something 🤷

```rust
#![feature(const_trait_impl)]

pub struct Outer<T>(Inner<T>);
struct Inner<T>(T);

impl<T> Unpin for Inner<T>
where
    T: const std::default::Default,
{}
```

Fixes rust-lang#149281
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jun 25, 2026
…t-bounds, r=fmease

Fix: auto trait, const trait bound

Split from rust-lang#158346. I was fiddling about with the code to understand the behaviour of the snippet below, got confused, and somehow fixed something 🤷

```rust
#![feature(const_trait_impl)]

pub struct Outer<T>(Inner<T>);
struct Inner<T>(T);

impl<T> Unpin for Inner<T>
where
    T: const std::default::Default,
{}
```

Fixes rust-lang#149281
rust-timer added a commit that referenced this pull request Jun 25, 2026
Rollup merge of #158390 - Jamesbarford:fix/trait-solver-const-bounds, r=fmease

Fix: auto trait, const trait bound

Split from #158346. I was fiddling about with the code to understand the behaviour of the snippet below, got confused, and somehow fixed something 🤷

```rust
#![feature(const_trait_impl)]

pub struct Outer<T>(Inner<T>);
struct Inner<T>(T);

impl<T> Unpin for Inner<T>
where
    T: const std::default::Default,
{}
```

Fixes #149281
@rustbot rustbot added the T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output. label Jul 6, 2026
@rust-log-analyzer

This comment has been minimized.

@lcnr

lcnr commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

good stuff, sgtm after fixing CI

@rust-log-analyzer

This comment has been minimized.

@Jamesbarford Jamesbarford force-pushed the fix/auto-trait-impl branch from 81e10c1 to 5592d3f Compare July 6, 2026 13:09
@rustbot

rustbot commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

Comment on lines +248 to +251
let errors = ocx.evaluate_obligations_error_on_ambiguity();
if errors.iter().any(|error| error.is_true_error()) {
return AutoTraitResult::NegativeImpl;
}

@lcnr lcnr Jul 6, 2026

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.

wait, that's just try_evaluate_obligations and then errors.is_empty() 😅

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've tidied up the commits and made that change

@Jamesbarford Jamesbarford force-pushed the fix/auto-trait-impl branch from 5592d3f to 84aa27d Compare July 6, 2026 16:02
@Jamesbarford Jamesbarford changed the title Avoid rustdoc auto-trait ICEs with the global next solver Prevent rustdoc auto-trait ICEs with -Znext-solver=globally Jul 6, 2026
@lcnr

lcnr commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@bors r+ rollup

@rust-bors

rust-bors Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 84aa27d has been approved by lcnr

It is now in the queue for this repository.

🌲 The tree is currently closed for pull requests below priority 6. This pull request will be tested once the tree is reopened.

@rust-bors rust-bors Bot 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 Jul 6, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jul 6, 2026
…r=lcnr

Prevent rustdoc auto-trait ICEs with `-Znext-solver=globally`

- Ensure rustdoc with `-Znext-solver=globally` does not ICE.

r? @lcnr
rust-bors Bot pushed a commit that referenced this pull request Jul 7, 2026
Rollup of 13 pull requests

Successful merges:

 - #158478 (Use correct typing mode in mir building for the next solver)
 - #158796 (Distinguish null reference production from null pointer dereference in UB checks)
 - #158821 (add regression test for late-bound type method probe ICE)
 - #158245 (Update the rustc_perf submodule)
 - #158346 (Prevent rustdoc auto-trait ICEs with `-Znext-solver=globally`)
 - #158536 (Instatiate binder instead of skipping when suggesting function arg error)
 - #158553 (Avoid infinite recursion when trying to build valtrees for self-referential consts)
 - #158679 (Document blocking guarantees for `std::sync`)
 - #158826 (Reorganize `tests/ui/issues` [19/N])
 - #158860 (Delete `impl_def_id` field from `ImplHeader`)
 - #158867 (rewrite `Align::max_for_target` in a more obvious way)
 - #158878 (borrowck: Inline `free_region_constraint_info()` to simplify the code)
 - #158881 (Update mdbook to 0.5.4)
@rust-bors rust-bors Bot merged commit 1c4fe1a into rust-lang:main Jul 7, 2026
13 checks passed
@rustbot rustbot added this to the 1.99.0 milestone Jul 7, 2026
rust-timer added a commit that referenced this pull request Jul 7, 2026
Rollup merge of #158346 - Jamesbarford:fix/auto-trait-impl, r=lcnr

Prevent rustdoc auto-trait ICEs with `-Znext-solver=globally`

- Ensure rustdoc with `-Znext-solver=globally` does not ICE.

r? @lcnr
@Jamesbarford Jamesbarford deleted the fix/auto-trait-impl branch July 7, 2026 11:42
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. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants