-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
rework winnowing to sensibly handle global where-bounds #132325
base: master
Are you sure you want to change the base?
Conversation
@bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
rework winnowing to sensibly handle global where-bounds this is somewhat weird, but it at least allows us to mirror this behavior in the new solver: - trivial builtin-impls - non-global where-bounds, bailing with ambiguity if at least one global where-bound exists - object ☠️ + alias-bound candidates - merge candidates ignoring global where-bounds r? `@compiler-errors`
☀️ Try build successful - checks-actions |
Finished benchmarking commit (6d565b7): comparison URL. Overall result: ✅ improvements - no action neededBenchmarking this pull request likely means that it is perf-sensitive, so we're automatically marking it as not fit for rolling up. While you can manually mark this PR as fit for rollup, we strongly recommend not doing so since this PR may lead to changes in compiler perf. @bors rollup=never Instruction countThis is the most reliable metric that we have; it was used to determine the overall result at the top of this comment. However, even this metric can sometimes exhibit noise.
Max RSS (memory usage)Results (primary -2.2%, secondary 0.6%)This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesResults (primary 2.3%)This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
Binary sizeResults (primary -0.1%)This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
Bootstrap: 784.701s -> 783.947s (-0.10%) |
@craterbot check |
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
🎉 Experiment
|
@bors try for later: |
rework winnowing to sensibly handle global where-bounds this is somewhat weird, but it at least allows us to mirror this behavior in the new solver: - trivial builtin-impls - non-global where-bounds, bailing with ambiguity if at least one global where-bound exists - object ☠️ + alias-bound candidates - merge candidates ignoring global where-bounds This is a different approach from rust-lang#124592 which maintains the "if there are global where-bounds, don't guide type inference using non-global where-bounds" behavior, hopefully avoiding the breakage and means we use guidance from non-global where-bounds in fewer, instead of in more cases. r? `@compiler-errors`
☀️ Try build successful - checks-actions |
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
🎉 Experiment
|
60f8fc5
to
88bca6a
Compare
76aa876
to
ba9767f
Compare
This comment has been minimized.
This comment has been minimized.
hello there :3 see the PR description for an in-depth summary of what's going on there. The actual changes in this PR are very limited, see the "How does this differ from stable" section. @rfcbot fcp merge |
This comment was marked as off-topic.
This comment was marked as off-topic.
Team member @lcnr has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
There may be multiple ways to prove a given trait-bound. In case there are multiple such applicable candidates we need to somehow merge them or bail with ambiguity. When merging candidates we prefer some over others for multiple reasons.
The approach in this PR1
We disable normalization via impls when using non-global where-bounds or alias-bounds, even if we're unable to normalize by using the where-bound.
Why this behavior?
inference guidance via where-bounds and alias-bounds
where-bounds
While the above pattern exists in the wild, I think that most inference guidance due to where-bounds is actually unintended. I believe we may want to restrict inference guidance in the future, e.g. limit it to where-bounds whose self-type is a param.
alias-bounds
Disable normalization via impls when using where-bounds
cc rust-lang/trait-system-refactor-initiative#125
If an impl adds constraints not required by a where-bound, using the impl may cause compilation failure avoided by treating the associated type as rigid.
This is also why we can always use trivial builtin impls, even for normalization. They are guaranteed to never add any requirements.
Lower priority for global where-bounds
A where-bound is considered global if it does not refer to any generic parameters and is not higher-ranked. It may refer to
'static
.This means global where-bounds are either always fully implied by an impl or unsatisfiable. We don't really care about the inference behavior of unsatisfiable where-bounds :3
If a where-bound is fully implied then any using an applicable impl for normalization cannot result in additional constraints. As this is the - afaict only - reason why we disable normalization via impls in the first place, we don't have to disable normalization via impls when encountering global where-bounds.
Consider true global where-bounds at all
Given that we just use impls even if there exists a global where-bounds, you may ask why we don't just ignore these global where-bounds entirely: we use them to weaken the inference guidance from non-global where-bounds.
Without a global where-bound, we currently prefer non-global where bounds even though there would be an applicable impl as well. By adding a non-global where-bound, this unnecessary inference guidance is disabled, allowing the following to compile:
There exist multiple crates which rely on this behavior.
Design considerations
We would like to be able to normalize via impls as much as possible. Disabling normalization simply because there exists a where-bound is undesirable.
For the sake of backwards compatability I intend to mostly mirror the current inference guidance rules and then explore possible improvements once the new solver is done. I do believe that removing unnecessary inference guide where possible is desirable however.
Whether a where-bound is global depends on whether used lifetimes are
'static
. The where-boundu32: Trait<'static>
is either entirely implied by an impl, meaning that it does not have to disable normalization via impls, whileu32: Trait<'a>
needs to disable normalization via impls as the impl may only hold for'static
. Considering all where-bounds to be non-global once they contain any region is unfortunately a breaking change.How does this differ from stable
The currently stable approach is order dependent:
This means that whether we bail with ambiguity or simply use the non-global where bound depending on the order of where-clauses and number of applicable impl candidates. See the tests added in the first commit for more details. With this PR we now always bail with ambiguity.
I've previously tried to always use the non-global candidate, causing unnecessary inference guidance and undesirable breakage. This already went through an FCP in #124592. However, I consider the new approach to be preferable as it exclusively removes incompleteness. It also doesn't cause any crater breakage.
How to support this in the new solver :o
This is not implemented in this PR and not part of this FCP!
To implement the global vs non-global where-bound distinction, we have to either keep
'static
in theparam_env
when canonicalizing, or eagerly distinguish global from non-global where-bounds and provide that information to the canonical query.The old solver currently keeps
'static
only theparam_env
, replacing it with an inference variable in thevalue
.rust/compiler/rustc_infer/src/infer/canonical/canonicalizer.rs
Lines 49 to 64 in a4cedec
I dislike that based on vibes and it may end up being a problem once we extend the environment inside of the solver as we must not rely on
'static
in thepredicate
as it would get erased in MIR typeck.An alternative would be to eagerly detect trivial where-bounds when constructing the
ParamEnv
. We can't entirely drop them as explained above, so we'd instead replace them with a new clause kindTraitImpliedByImpl
which gets entirely ignored except when checking whether we should eagerly guide inference via a where-bound. This approach can be extended to where-bounds which are currently not considered global to stop disabling normalization for them as well.Keeping
'static
in theparam_env
is the simpler solution here and we should be able to move to the second approach without any breakage. I therefore propose to keep'static
in the environment for now.r? @compiler-errors
Footnotes
see the source for more details ↩
they don't constrain inference and don't add any lifetime constraints ↩
a where-bound is global if it is not higher-ranked and doesn't contain any generic parameters,
'static
is ok ↩we arbitrary select a single object and alias-bound candidate in case multiple apply and they don't impact inference. This should be unnecessary in the new solver. ↩ ↩2
Necessary for
dyn Any
and https://github.com/rust-lang/rust/issues/57893 ↩global where-bounds are only used if they are unsatisfiable, i.e. no impl candidate exists ↩