Skip to content

Find a type variable by what it was copied from, not by its name - #1244

Merged
Frotty merged 3 commits into
masterfrom
fix/canonical-typevar
Aug 17, 2026
Merged

Find a type variable by what it was copied from, not by its name#1244
Frotty merged 3 commits into
masterfrom
fix/canonical-typevar

Conversation

@Frotty

Copy link
Copy Markdown
Member

EliminateGenerics found a type variable's position by comparing names when identity failed. The backlog called this a name-tolerant lookup compensating for several nodes standing for one source parameter, and called removing it mechanical. Neither was quite right.

What it actually was

Removing the name comparison broke most of the generics tests, not an edge case — so identity never held on the common path. Tracing which nodes failed to match, the target was always detached: a type variable belonging to nothing.

Three places copy a function's or a class's type variables and then empty the copy's list:

  • specializeFunction
  • the specialised method implementation beside it
  • specializeClass

copyWithRefs copies the type variables along with the body, so the references inside the copy point at the new nodes; emptying the list then leaves those nodes with no owner. The name comparison was not compensating for the occasional duplicate — it was the only thing holding those references together.

The change

Each copy is recorded against what it was made from, before the list is emptied, and the lookup compares those. Same shape as the field origin map in #1239, and for the same reason: the relationship is known where the copy is made and nowhere afterwards.

After pairing all three sites, no lookup falls back to a name, so the comparison is gone from indexOfTypeVar and from inheritTypeClassBinding through it. Two parameters which merely share a name can no longer be taken for one where dispatch is resolved.

What is left

ProgramState.getCurrentTypeArgument still matches on names. The record lives on the ImTranslator, and the interpreter is handed a program rather than the translation which produced it, so reaching it means threading the translator through the interpreter — four construction sites, three of them tests, but an API change all the same. The comment there now says that, rather than leaving it looking like an oversight, and backlog item 10 carries what remains.

On the test

aMethodTypeParameterMayShareTheClassParameterName covers a class and a method declaring a parameter of the same name. It passes without this change as well as with it, so it does not pin the fix — the lookups are not reached with both parameters in one list by that shape, and I did not find a program which reaches them that way. It is kept for the language case, and its comment says as much. The change rests on removing the name comparison and on the suite showing nothing depended on it.

Full suite green, 7m41s.

Three places copy a function's or a class's type variables and then empty the
copy's list, which leaves every reference inside the copy pointing at a node
with no owner at all. Comparing names was not compensating for the occasional
duplicate the note here described: it was the only thing holding those
references together, and removing it broke most of the generics tests.
Recording each copy against what it was made from, before the list is emptied,
replaces it. Name matching is gone from indexOfTypeVar and from
inheritTypeClassBinding through it, so two parameters which merely share a name
can no longer be taken for one where dispatch is resolved.
The interpreter's lookup still matches names. The record lives on the
ImTranslator and the interpreter is handed a program rather than the
translation which produced it; the comment there says so rather than leaving it
looking like an oversight.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:0192629f91

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

Comment on lines +401 to +404
* It passes without the canonical type variable change as well as with it, so it does not pin
* that change — the lookups which used to compare names are not reached with both parameters in
* one list here, and a program which does reach them that way was not found. It is kept for the
* language case rather than for the fix.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exercise the changed type-variable lookup in the regression

As this comment acknowledges, the added test passes before the fix: convert declares an unbounded T and never dispatches on it, so indexOfTypeVar never has to distinguish the method parameter from the class parameter. Consequently, omitting or breaking any of the new origin registrations would leave the suite green. Make the method parameter bounded, dispatch through it using a type-class implementation distinct from the class parameter's implementation, and assert the selected result so this bug fix has the required failing repro.

AGENTS.md reference: AGENTS.md:L62-L64

Useful? React with 👍 / 👎.

The earlier test gave a class and a method a same-named parameter and left the
method's unbounded, so nothing ever dispatched on it and the lookup was never
asked to tell the two apart. It passed either way, which I said in the pull
request rather than fixing.
Bounding the method's parameter and giving it a different instance is what makes
both reach the same lookup. The class is built over int, whose instance doubles;
the method is called with string, whose instance answers 7. Without the change
the string is dispatched through the int instance and the interpreter dies
casting it to a number, so the bug was a wrong dispatch rather than the tidiness
the backlog described.
@Frotty

Copy link
Copy Markdown
MemberAuthor

You were right, and the recipe was the missing piece. aBoundedMethodParameterMayShareTheClassParameterName now fails without the change.

What my version was missing: the method's parameter was unbounded, so nothing ever dispatched on it and indexOfTypeVar was never asked to tell it from the class's. Bounding it and giving it a different instance is what makes both parameters reach the same lookup.

class Holder<T: Show> // built over int, whose instance doubles
function shown() returns int
return T.show(held)
function convert<T: Show>(T other) returns int // called with string, whose instance answers 7
return T.show(other)

With the change, h.shown() == 42 and h.convert<string>("x") == 7. Without it:

ClassCastException: ILconstString cannot be cast to ILconstNum

The string argument is dispatched through the int instance, which does x * 2. So this was a wrong dispatch rather than the hygiene cleanup the backlog item described — worth saying, because I had accepted that framing and it made the change look less necessary than it is.

I have also corrected the item's note, which claimed a same-named parameter test passes either way. That was true of the weak version and misleading as a general statement; it now records that the bound and the second instance are what bring the two parameters into one lookup.

Full suite green, 7m48s.

@codex review

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:0dfd6b91a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

Comment on lines +405 to +407
@Test
public void aBoundedMethodParameterMayShareTheClassParameterName() {
testAssertOkLines(true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add Lua coverage for the corrected dispatch lookup

testAssertOkLines(true, ...) exercises the pre-transform interpreter and Jass pipeline, but not transformGenericNewOnly, where Lua consumes the changed origin tracking through a different specialization path. Please run this same bounded class/method-name collision with test().testLua(true).executeProg() so a Lua-only wrong-instance regression cannot pass unnoticed; the repository explicitly requires compiler features to be validated on both backends.

AGENTS.md reference: AGENTS.md:L215-L221

Useful? React with 👍 / 👎.

Asked for Lua coverage of the corrected lookup, and it cannot be given: the
only shape which brings two same-named parameters into one lookup is a class
parameter beside a method parameter, and Lua rejects that with "Generics should
match class method type variables" - on master as well, so it is a gap rather
than a regression.
A version Lua does compile, with the second parameter on a free function, was
written and then deleted: it passes with the change and without it, because the
two parameters never reach one lookup that way, so it would have claimed
coverage it does not provide.
The rejection is pinned instead, and the gap behind it is recorded as item 25
with where to start - the arity check counts a method's own parameter against a
list that transformGenericNewOnly never lifted the class's variables onto.
@Frotty

Copy link
Copy Markdown
MemberAuthor

Tried, and Lua coverage of this fix cannot be given. What I found instead is a gap worth recording.

The shape does not compile on Lua. The only program which brings two same-named parameters into one lookup is a class parameter beside a method parameter, and that is rejected on Lua with Generics should match class method type variables — on master as well as here, so it is pre-existing rather than something this change causes.

A Lua-compatible variant covers nothing. I wrote one with the second parameter on a free function rather than a method. It compiles and runs on Lua, and it passes with the change and without it: the two parameters never reach one lookup that way. I checked it against master before believing it, and deleted it rather than ship a test whose name claims more than it checks — the same mistake as the version you caught.

So the Lua behaviour is pinned as a rejection, aBoundedMethodParameterInAGenericClassIsRejectedForLua, with the reason in its comment. Should the gap be fixed, that test fails and gains the dispatch assertion the interpreter one already has.

The gap is backlog item 25, with where to start: the message comes from the arity check between a call's generics and the callee's type variables. On the Jass path a method of a generic class has the class's variables lifted onto it; transformGenericNewOnly does not lift them, so the method's own parameter is counted against a list which does not include the class's.

The interpreter repro from the previous round is unchanged and still fails without the fix, with the ClassCastException from dispatching a string through the int instance.

Full suite green, 8m.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit:1f02868092

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

@Frotty
Frotty merged commit 2d4ae86 into masterAug 17, 2026
6 checks passed
@Frotty
Frotty deleted the fix/canonical-typevar branch August 17, 2026 09:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Frotty