Repository navigation
Resolve inferred generics through the generics they're inferred from - #265
Merged
Merged
Conversation
- Resolve generics through the type expected of a call when that's all that gives a generic a type - Test a recursive call that depends on generics in scope not being resolved through, and upper bound candidates that are other generics - Compute which generics each one depends on once, and drop a buffer that wasn't needed
- Resolve a generic that's only bounded by others in a cycle through the ones that were resolved, or the type expected of the call - Test generics declared out of order, and a type inferred through another generic that a later argument can't take
- Resolve generics that are only bounded by their cycle in rounds, so one resolved through a partner or the expected type is passed on to the others - Test a cycle of three resolved in turn, a cycle of four, and a cycle where only one generic has an expected type - Test held candidates resolved through other generics, and compound candidates in cycles left as is - Say what the swapped recursive call does and doesn't cover
Rigidity
force-pushed
the
rigidity/issue-146-transitive-inference
branch
from
November 14, 2026 04:36
8dba60b to
a3c4ba6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #146
Problem
Passing a generic function to a parameter that relates two of the callee's generics failed:
The candidates collected were
A: [Int]andB: [A].resolve_inferencemapped each generic on its own, to{A: Int, B: A}, andsubstitute_with_mappingsdoesn't apply mappings transitively. That leftfn(a: Int) -> Aand the return typeA. I confirmed this by logging the mappings on main.Change
resolve_inferenceresolves a candidate that references another generic being inferred through what that generic is inferred as, resolving the generics it depends on first.A: [B, Int], B: [A]) are resolved together. A candidate that's exactly another generic in the cycle is replaced by that generic's candidates of the same kind (lower, upper or held).A: B, B: Ais still rejected. This is done in rounds, so that one resolved this way is passed on to the others in turn (a cycle of three bounded at one generic, or one where only one generic has an expected type). Each round reads the same mappings, so the order of the generics doesn't matter.Inference::set_in_scope. The compiler marks the callee's generics that are in scope at the call, which happens in a recursive call to the function itself. These aren't resolved through, because a candidateAthere can be the caller's own rigidA. Main already relies on this for something likeswap(n - 1, y, x)insideswap<A, B>, which gives{A: B, B: A}.resolve_inference_with_fallback. The call's final resolution uses the type expected of the call for generics that nothing else constrains, so generics inferred from them resolve through it, e.g.let g: fn(a: Int) -> Int = compose(identity). Before, the fallback was merged in after resolution.has_uninferred_parameters) must only see what the arguments inferred, and including the fallback there changedgeneric_inference_lambda_order.Inference still only proposes types. The final check of each argument against the substituted parameter type is unchanged, so the change makes more valid programs compile and rejects nothing new.
Tests
tests/types/generic_inference_transitive.ruecovers:xbeforef)map<B, A>){A: B, B: A}behaviour, and one fails if the in-scope marking is removedtests/types/generic_inference_transitive_errors.ruecovers calls that must still be rejected:Athat a later function can't takerue-typescover lower, upper, held and compound candidates, compound candidates in cycles being left as is, cycles in both generic orders, unconstrained cycles, generics in scope, and fallback types.No existing snapshots changed.
Provisional decisions, for you to confirm
Buninferred inlet r = map(id, 5), and it would break the recursiveswapcase.map(id, x)insidemap, keeps main's behaviour. The fuller fix would rename the callee's generics in recursive calls so both meanings can coexist. That's a larger change infunction_call.rs.BofAgivesAthe lower candidates ofB. The reverse relation,Aappearing inB's upper list, isn't propagated. Sofeed(identity, inc)withA ≤ B ≤ Intstill can't inferA, as on main (Infer generics bounded from above through another generic #285).Follow-ups, not in this PR
map(duplicate, 42)withfn duplicate<T>(x: T) -> (T, T)still fails.B's candidate is(T, T), whereTisduplicate's own generic leaking out of the instance-bound check. That's a different cause, in candidate collection incompare_impl(Inference leaks a generic function's own generic into the candidates #283).let r: 5 = map(identity, 5)is rejected, as it is on main.add_expected_boundschecksB's unresolved candidateAagainst5(Expected literal type doesn't reach generics inferred through other generics #284).