Make type substitution carry what is known about a type - #1229
Merged
Conversation
Substitution was two parallel lists -- a function's or class's type variables next to the arguments given at a use -- paired up positionally at each point of use. Pairing them unwrapped the ImTypeArgument and returned the bare ImType, so everything else the argument carried was dropped. A type class binding lives on the argument, which meant it could not survive being substituted: callers that needed it re-attached it by hand, and the ones with nowhere to re-attach it from silently produced an argument with no instance. Introduce TypeSubst, one primitive for the operation. Substituting into an argument position now yields the argument that was bound, so the instance travels with the type it belongs to. Lookup is by identity, matching what positional lookup already did, since IM nodes do not override equals. TypeRewriter missed one type variable reference: the one held directly by ImTypeVarDispatch rather than as a type. A closure lifting its body into a class of its own therefore left the dispatch naming a variable of the function it had left, with nothing able to bind it. Rewriting it with the rest is what makes a bound usable from inside a closure. Also drops ProgramState.substituteTypeVars, a second implementation of substitution that disagreed with the first about whether bindings survive, and was only ever reached from itself.
Member
Author
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
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.
Substitution of type variables was expressed as two parallel lists — the type variables of a
function or class next to the type arguments given at a use of it — paired up positionally at each
point of use:
An
ImTypeArgumentis a type plus what is known about it, but pairing the lists unwrapped theargument and returned the bare
ImType. The type class binding lives on the argument, so it couldnot survive substitution. Callers that needed it re-attached it by hand; the ones with nowhere to
re-attach it from silently produced an argument with no instance.
This replaces that with one primitive,
TypeSubst. Substituting into an argument position nowyields the argument that was bound, so the instance travels with the type it belongs to. Lookup is
by identity, which is what positional lookup already did — IM nodes do not override
equals.What it fixes
A type class bound is now usable from inside a closure:
Two things were wrong. Substitution dropped the binding on the way into the closure class, and
TypeRewriternever rewrote the one type variable reference that is held directly rather than as atype — the one on
ImTypeVarDispatch. A closure lifting its body into a class of its own thereforeleft the dispatch naming a variable of the function it had left, with nothing able to bind it.
Jass only for now. Lua keeps generics erased and specialises what it can reach through a concrete
type; a closure is reached through its interface, so the specialised class gets built but nothing
calls it. That is a change to Lua's erasure rather than to substitution, and is left alone here —
dispatchInsideClosureIsRejectedForLuapins that it is reported clearly rather than mistranslated,and becomes the success case if that is ever fixed.
Also
ProgramState.substituteTypeVars, a second implementation of substitution that disagreedwith the first about whether bindings survive, and was only ever reached from itself. It also
silently did not recurse into array and tuple types.
substituteTypeno longer rebuilds a type when there is nothing to replace. It runs for thereturn type of every call, and was allocating an equal copy of every class and tuple type.
Verified
for the closure case).
0d2bb0fbdacross the tests that produce it (28 files, nodifferences), and byte-identical across two runs of this branch — AGENTS.md §8.
TypeSubstTestscovers the primitive directly: binding survives in argument position, throughnesting, and applied to an argument; a binding already present wins; variables sharing a name stay
distinct; a missing argument is tolerated until it is named.
Worth knowing, unrelated to this change: emitted Jass is not stable across test runs. Comparing
two runs of
0d2bb0fbdagainst each other gives 91 differing.jfiles, the same magnitude as thisbranch against itself. The differences are temp names (
temp151vstemp8) from a counter that isnot reset between compilations in one JVM, so the output depends on how many tests ran before. It is
a test-harness artefact rather than a defect in compiling a single map, but it does mean
.joutputcannot be diffed across runs to check a change.
Not in scope
The named follow-ups from #1226 stand: Lua constructor and closure dispatch, module bounds, and the
stdlib conversion of
HashMap/HashList/HashSet/Tableto useIndexable. One name-toleranttype-variable lookup remains in
EliminateGenerics.inheritTypeClassBinding, covering the in-placerewrite of call-site type arguments; it can go once a type parameter is guaranteed one
ImTypeVar.