Skip to content

Fix marker trait winnowing depending on impl order#153847

Open
traviscross wants to merge 2 commits into
rust-lang:mainfrom
traviscross:TC/fix-typeoutlives-missing-param-check
Open

Fix marker trait winnowing depending on impl order#153847
traviscross wants to merge 2 commits into
rust-lang:mainfrom
traviscross:TC/fix-typeoutlives-missing-param-check

Conversation

@traviscross
Copy link
Copy Markdown
Contributor

@traviscross traviscross commented Mar 14, 2026

Story: I was looking through this code for an unrelated reason and happened to notice the duplicate condition. That seemed obviously wrong, especially in light of the comment, so I worked backward to what it affected, wrote a test that failed, and then found that the test matched an existing known-bug test, which pointed me to #109481.


The TypeOutlives handler in evaluate_predicate_recursively means to check whether a type in a T: 'a predicate has free regions, bound regions, non-region inference variables, or non-region generic parameters -- and if so, to return EvaluatedToOkModuloRegions rather than EvaluatedToOk. Correspondingly, the comment says "no free lifetimes or generic parameters". But the code was mistakenly checking has_non_region_infer() twice instead of checking both has_non_region_infer() and has_non_region_param().

This meant that TypeOutlives(T, 'static) where T is a type parameter returned EvaluatedToOk -- claiming the result holds unconditionally -- when the correct answer is EvaluatedToOkModuloRegions.

The distinction matters during marker trait winnowing in prefer_lhs_over_victim, which uses must_apply_considering_regions() (true only for EvaluatedToOk) to decide whether one impl beats another when there are multiple candidates. With the bug, a T: 'static-bounded impl appeared equally as strong as an unrestricted impl, making the winner depend on source order. This caused spurious E0310 errors when the more-constrained impl happened to appear after the less-constrained one.

Fixes #109481.

This same symptom was originally filed as #84917 and fixed in PR #88139. Then PR #102472 rewrote the TypeOutlives handler, introducing the duplicate has_non_region_infer() and losing the param check, regressing this. Around this same time, #109481 was filed. It noted the connection to #102472 -- the bug only appeared after it -- but the duplicate condition was not noticed.

r? @lcnr

cc @oli-obk @nikomatsakis

@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. labels Mar 14, 2026
@rustbot

This comment was marked as resolved.

@traviscross traviscross force-pushed the TC/fix-typeoutlives-missing-param-check branch 5 times, most recently from 411054e to 85b6665 Compare March 14, 2026 07:08
@traviscross traviscross added T-types Relevant to the types team, which will review and decide on the PR/issue. F-marker_trait_attr `#![feature(marker_trait_attr)]` labels Mar 14, 2026
Comment thread tests/ui/marker_trait_attr/overlapping-impl-1-modulo-regions.rs
@traviscross traviscross force-pushed the TC/fix-typeoutlives-missing-param-check branch from 85b6665 to 516f56b Compare March 14, 2026 07:47
@lcnr
Copy link
Copy Markdown
Contributor

lcnr commented Mar 15, 2026

Good catch. We actually need to check for both.

If we have ?x: 'a, ?x could later be inferred to be T after all

@traviscross
Copy link
Copy Markdown
Contributor Author

traviscross commented Mar 22, 2026

Good catch. We actually need to check for both.

If we have ?x: 'a, ?x could later be inferred to be T after all

That makes sense to me. On the other hand, I commented out the pred.0.has_non_region_infer() check and all the tests still pass. I timed out in trying to come up with a test that would fail. I think maybe things are being equated and normalized before we get here (but you'd know better on this). Even still, it would seem more correct to me to not lean on that and to check for both.

@lcnr
Copy link
Copy Markdown
Contributor

lcnr commented Mar 30, 2026

I timed out in trying to come up with a test that would fail. I think maybe things are being equated and normalized before we get here (but you'd know better on this). Even still, it would seem more correct to me to not lean on that and to check for both.

We don't even try to prove things if the self type is an inference variable, so you need sth like

#![feature(marker_trait_attr)]
#[marker]
trait Marker<T> {}

impl<T: 'static> Marker<T> for u32 {}
impl<T> Marker<T> for u32 {} 

fn foo<T: Marker<U>, U>() -> U { todo!() }

fn bar<'a>() {
    let x = foo::<u32, _>();
    1i32.abs(); // guarantees we do trait solving before constraining `U`
    let _: *mut &'a () = x;
}

would need to look at the logging in select/mod.rs to see whether this does what u need

@wesleywiser
Copy link
Copy Markdown
Member

Just checking in from compiler team triage, I think this is waiting on a review from @lcnr. Is that right?

@lcnr
Copy link
Copy Markdown
Contributor

lcnr commented May 14, 2026

I don't necessarily think so, either me or @traviscross needs to look into my example #153847 (comment) and figure out whether that's works as a test

@lcnr lcnr added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 14, 2026
@traviscross traviscross force-pushed the TC/fix-typeoutlives-missing-param-check branch from 516f56b to 7259e03 Compare May 20, 2026 20:31
@rustbot

This comment was marked as resolved.

The `TypeOutlives` handler in `evaluate_predicate_recursively` means
to check whether a type in a `T: 'a` predicate has free regions,
bound regions, non-region inference variables, or non-region generic
parameters -- and if so, to return `EvaluatedToOkModuloRegions`
rather than `EvaluatedToOk`.  Correspondingly, the comment says "no
free lifetimes or generic parameters".  But the code was mistakenly
checking `has_non_region_infer()` twice instead of checking both
`has_non_region_infer()` and `has_non_region_param()`.

This meant that `TypeOutlives(T, 'static)` where `T` is
a type parameter returned `EvaluatedToOk` -- claiming the
result holds unconditionally -- when the correct answer is
`EvaluatedToOkModuloRegions`.

The distinction matters during marker trait
winnowing in `prefer_lhs_over_victim`, which uses
`must_apply_considering_regions()` (true only for `EvaluatedToOk`)
to decide whether one impl beats another when there are multiple
candidates.  With the bug, a `T: 'static`-bounded impl appeared
equally as strong as an unrestricted impl, making the winner
depend on source order.  This caused spurious E0310 errors
when the more-constrained impl happened to appear after the
less-constrained one.

This fixes Rust issue 109481.  See PR 153847.

This same symptom was originally filed as issue 84917 and fixed in PR
88139.  Then PR 102472 rewrote the `TypeOutlives` handler, introducing
the duplicate `has_non_region_infer()` and losing the param check,
regressing this.  Around this same time, issue 109481 was filed.  It
noted the connection to PR 102472 -- that the bug only appeared after
it -- but the duplicate condition was not noticed.
It's harder than one would imagine to demonstrate that the
`has_non_region_infer()` check in the `TypeOutlives` handler is
load bearing (even though the check seems right analytically).
Fortunately, we did find a way to show this.  Let's add that test.

In working that out, we found two other interesting ways of showing
that the `has_non_region_param()` check matters.  Let's add those too.

Though we're concerned here with code in the old solver, we test
against both.
@traviscross traviscross force-pushed the TC/fix-typeoutlives-missing-param-check branch from 7259e03 to 9a122c9 Compare May 20, 2026 20:35
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented May 20, 2026

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.

@traviscross
Copy link
Copy Markdown
Contributor Author

traviscross commented May 20, 2026

Thanks. I looked into that example. It didn't work as a test of the has_non_region_infer() check (in the TypeOutlives handler), but I added (a variant of) it as an interesting test of the has_non_region_param() side of the disjunction. I did eventually construct something that worked for exercising the has_non_region_infer() side. It's pretty subtle.

@rustbot review

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

F-marker_trait_attr `#![feature(marker_trait_attr)]` 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-types Relevant to the types team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

marker traits, cannot prefer impl with no bounds

5 participants