Skip to content

Avoid rustdoc auto-trait ICEs with the global next solver#158346

Open
Jamesbarford wants to merge 2 commits into
rust-lang:mainfrom
Jamesbarford:fix/auto-trait-impl
Open

Avoid rustdoc auto-trait ICEs with the global next solver#158346
Jamesbarford wants to merge 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

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

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants