or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Syncing
xxxxxxxxxx
deferred ProjectionEq deep dive 9/9/22
zulip meeting
Note: this doc often refers to a
ProjectionEq
obligation, this is actually calledProjectionPredicate
in rustc apparently which seems like kind of a silly name to me. Apologies for constantly messing up the name in this doc and likely anywhere I talk about this lolprojection equality status quo
currently relating projections happens "eagerly", for example
<_ as Trait>::Assoc eq u32
fails because we can't wait around to see if the projection normalizes tou32
.Other type is not a projection
If we relate
<_ as Trait>::Assoc eq SomeOtherType<...>
it will currently fail which is incorrect as it may later end up with an inferred self type which allows for normalization, i.e.<u64 as Trait>::Assoc
.This currently is mitigated by sometimes normalizing
<_1 as Trait>::Assoc
to some new inference var_2
and emitting aProjectionEq
obligation. However this falls short for higher ranked stuff which can be gotten from where clauses i.e.where for<'a> <T as Trait<'a>>::Assoc: Other
, causing issues #96230 and #89196.Other projection is a different
DefId
If we relate:
<_1 as Trait>::Assoc eq <_2 as Trait>::Assoc2
currently it would fail because the projections are for different defids. This is overly restrictive as
_2
might end up getting resolved to some concrete typeT
allowing the projection to be normalized to:<T as Trait>::Assoc
which would then be able to succeed in being equated with:
<_1 as Trait>::Assoc
Relating substs
If we relate
<u32 as Trait<_1>>::Assoc eq <u64 as Trait<_1>>::Assoc
currently it would fail becauseu32 eq u64
does not hold. This is overly restrictive is the implu32: Trait<_1>
andu64: Trait<_1>
may have the same value for thetype Assoc
.Similarly if we relate
<_1 as Trait>::Assoc eq <_2 as Trait>::Assoc
we would constrain_1 eq _2
which may not necessarily be required and later cause type inference to error because_2
turned out to be au32
and_1
turned out to be a u64.deferred projection equality
PR #96912 changes the logic of relating projections to (if the projection has infer vars) succeed and emit a
ProjectionEq
obligation. This seems to fix all of the above issues (surprisingly as I was only aiming to fix the first issue).As of writing this the PR updates
ProjectionEq
obligation handling to only relate the substs and defid of aProjectionTy
if it is "as normalized as it will ever get". I am not sure what precise terminology for this is but the idea is that<T as Trait>::Assoc
is never going to be able to normalized more, but a<_ as Trait>::Assoc
might.Fixing "relating substs" is not entirely backwards compatible as now relating
<_ as Trait>::Assoc eq <T as Trait>::Assoc
will leave the inference var unconstrained, it would be simple to "unfix" this in the PR.Note: the exact code changes in #96912 are a bit
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →project.rs
and winnowing, they certainly will be changed before seriously attempting to land the PR. (More about the winnowing changes below.)#96912 issues
typenum regression
below is a minimal repro of the code that fails in typenum with the lazy projection equality PR, annotated with the candidates/obligations that are being evaluated during winnowing:
The comments in the code shows all the candidates for each obligation being solved and the nested obligations from each candidate. When evaluating the
impl
candidate we end up evaluting infinitely recursingFoo<..<..<Foo<A>..>..>: Shl< <Foo<_> as NotSub>::Output >
obligations.In order to make the code compile we need rustc to understand that the
impl
candidate doesn't apply and theparam
candidate does. The impl candidate canEvaluateToErr
if the obligations are evaluated in a specific order:Foo<_#1t>: NotSub
this unifies_1 eq B
Foo<Foo<Foo<A>>>: Shl< <Foo< _1 > as NotSub>::Output >
ProjectionEq(<Foo<B> as NotSub>::Output == Foo<_2>)
note that this is actuallyFoo<_1> as NotSub
but we have unified_1 eq B
which allows us to know that thisProjectionEq
obligation does not hold.Currently we do not even evaluate the
ProjectionEq
obligation, we evalauteFoo<Foo<Foo<Foo<A>>>>: Shl< Foo<_2> as NotSub >::Output
first and thenFoo<Foo<Foo<Foo<Foo<A>>>>>: Shl<...>
etc etc. This causes us to get a recursion limit error which ends compilation rather than anEvaluatedToErr
which would let us reject the impl candidate and move on to successfully evaluating theparam
candidate.We could do a lot better at avoiding these recursion errors by removing
evaluate_predicate_recursively
and usingObligationCtx
(honestly I don't know what type to use but lcnr mentioned usingObligationCtx
so…) to evaluate candidates. This would ensure that evenf ifProjectionEq
was evaluated and thenFoo<_1>: NotSub
was evaluated it would be alright since theProjectionEq
obligation could stall on_1
. Using anObligationCtx
also removes the possibility of us recursing forever without evaluating some obligations (This is more of an assumption, I don't actually know enough to know if this is completely true…).An alternative way to make this specific code compile would be to take advantage of the fact that impl candidates get discarded in favor of param candidates. We could potentailly evaluate all param candidates first and if any are
EvaluatedToOk
, discard all the impl candidates without evaluating them. There is a perf run for this solution at #97380.The PR currently implements the latter solution (evaluate param candidates first). It would not be backwards compatible to remove this and replace it with only fulfill like evaluation as
ObligationCtx
does not prevent us from ever getting a recursion limit error. UsingObligationCtx
seems like a nicer solution and something that would be good to do anyway so that we don't have to maintain two separate ways of evaluating obligations.projection eq variance
if we have a
<_ as Trait>::Assoc sub Foo<'a>'
and later_
gets inferred toFoo<'b>
which allows us to normalize the projection toFoo<'b>
then we would end up doingFoo<'b> eq Foo<'a>
notFoo<'b> sub Foo<'a>
.Questions
How do I ask a question
Like this.
@lcnr: nested fulfillment during evaluation
evaluate_predicate_recursively
sucks, using a new fulfillment context there instead should work but allows some code to compile which may break again when going "full chalk". How does it work in chalk rn?nikomatsakis (spying in, not 100% here) – I think that in chalk-style solver we would evaluate both paths but discard the impl path (eventually) as ambiguous – but probably not until types got too large or something, I have to look more closely at that.
implications merging 97380
codes kinda janky and would not be entirely safe to remove later