@@ -13,35 +13,23 @@ Notes rather than leaving it in a commit message.
1313Numbering is stable: finished items leave a gap rather than shifting the ones below,
1414because ` LOOP.md ` refers to items by number.
1515
16- 16 . ** A never-written array of a type parameter reads as nothing, silently.** In the interpreter
17- only — Jass and Lua both give the type argument's default. ` DefaultValue.get(ImTypeVarRef) `
18- returns ` ILconstUnsafeDefault ` , whose ` isEqualTo ` matches only another ` ILconstUnsafeDefault ` ,
19- so a comparison against the real default is quietly false rather than an error. Repro:
20-
21- class Box<T:>
22- private static T array none
23- static function first() returns T
24- return none[0]
25- init
26- if Box<int>.first() == 0
27- testSuccess()
28-
29- Passes on every Jass configuration and fails on the pre-transform interpreter run. The plain
30- ` int array ` version passes, so this is specific to the type parameter. The interpreter knows
31- the current type argument (` ProgramState.resolveType ` ), but ` DefaultValue ` is a static
32- attribute with no access to it, and the array's default supplier is bound when the array is
33- allocated rather than when it is read. Either resolve at read time where the state is in hand,
34- or make the placeholder throw when used — what it must not do is compare unequal in silence.
35- Found by ` FastHashMapTests ` : the tombstone fixture needs a "no value" for ` V ` .
36-
37- 15 . ** One junk dispatch slot per specialised class.** Left over from item 3, same heuristic in
38- the other place it is used. ` addDirectAliases ` composes `owner.getName() + "_ " +
16+ 15 . ** One junk dispatch slot per specialised class.** ` addDirectAliases ` and
17+ ` LuaTranslator.collectDispatchSlotNames ` both compose `owner.getName() + "_ " +
3918 semanticNameFromMethodName(name)`, and for a specialised method that trailing segment is the
40- type argument, so every method of ` FastHashMap<int, int> ` claims the same
41- ` FastHashMap_specialized_integer__integer_integer ` slot and the alphabetically first wins.
42- Nothing calls it, so it is dead weight rather than a wrong result — but it is the same
43- mistake, and the alias it * should* produce is the class qualified with the declared name.
44- Fixing it changes emitted slot names, so it wants its own commit and its own suite run.
19+ type argument — so every method of ` FastHashMap<int, int> ` claims one shared
20+ ` FastHashMap_specialized_integer__integer_integer ` slot and the alphabetically first wins it.
21+ Nothing calls it, so it is dead weight rather than a wrong result.
22+
23+ Tried using the declared name instead and reverted it: overloads share a declared name, so
24+ ` setup(int) ` and ` setup(string) ` collapse into one slot, which is what
25+ ` LuaTranslationTests.overloadedMethodsDoNotAliasInLuaDispatchTables ` and
26+ ` moduleProvidedOverloadedOverrideDoesNotCollapseLuaSlots ` exist to prevent. Both sources of a
27+ semantic name are wrong, in opposite directions: the mangled trailing segment collides across
28+ the siblings of one specialisation, the declared name collides across overloads. A fix needs a
29+ name that separates both — the declared name together with the dispatch signature key would,
30+ since that is already what distinguishes overloads elsewhere in the same file. Worth doing only
31+ if this stops being dead weight, because the cost of getting it wrong is a real mis-binding
32+ while the cost of leaving it is one unused table key per specialised class.
4533
46346 . ** Lua dispatch inside the constructor** of a bounded generic class. Works on Jass; there is now
4735 a repro for both targets, ` TypeClassTests.dispatchInsideConstructor ` and
@@ -55,15 +43,20 @@ because `LOOP.md` refers to items by number.
5543 outermost one a concrete argument. ` collectGenericNewUse ` requires non-empty type arguments, so
5644 it never starts.
5745
58- The instantiation is only on the type of what the call is assigned to. Three ways to get at it,
59- roughly in order of how much they would disturb: attach the class's type arguments to
60- constructor calls when the intermediate language is built, which is where the frontend still
61- knows them and would serve both targets uniformly — but it changes the Jass path, which reaches
62- the same answer another way today, so the emitted ` .j ` needs checking; read them from the
63- assignment target on the Lua path, which is a syntactic shape and would miss
64- ` foo(new Box<int>(21)) ` ; or specialise from the ` #alloc ` inside the constructor, which is the
65- item 5 mechanism but would have to reach back out to the caller. The first looks right; confirm
66- it is what the Jass path already relies on before changing it.
46+ What Jass does, from ` TypeClassTests_dispatchInsideConstructor_no_opts.jim ` : it specialises the
47+ constructor function itself, ` b_8 = new_Box⟪integer⟫(21) ` . It gets there from * types* , not from
48+ the call — ` collectGenericUsages ` collects a ` GenericVar ` for the local declared
49+ ` Box<integer{show}> ` and a ` GenericReturnTypeFunc ` for ` new_Box ` , whose return type is generic.
50+ The Lua collector has neither; it only ever looks at calls. So attaching type arguments to
51+ constructor calls, which an earlier note here proposed, is not what the Jass path relies on and
52+ would be a second mechanism rather than the same one.
53+
54+ The honest next step is to collect from types on the Lua path too, restricted the way item 5's
55+ collection is. That runs straight into the same design question, though: ` GenericVar ` and
56+ ` GenericReturnTypeFunc ` specialise the * class* , and item 5 showed that an object coming from a
57+ specialised class while its methods are bound to the erased one breaks everything. Either the
58+ collection has to specialise only the constructor path and leave the object erased, or Lua stops
59+ erasing constructed generic classes — which is a decision about the erasure model, not a patch.
6760
68617 . ** Module bounds.** ` module M<T: Show> ` is rejected with a clear message today. Needs
6962 receiver rewriting during expansion, or type parameters on ` ModuleInstanciation ` .
@@ -126,6 +119,24 @@ because `LOOP.md` refers to items by number.
126119
127120## Blocked on a decision
128121
122+ - ** 8. Should ` div ` and ` mod ` keep returning the left operand's type?** Tried returning
123+ ` WurstTypeInt.instance() ` to match ` caseMathOperation ` and reverted it: it is a user-visible
124+ breaking change, and the suite already defines the current behaviour as correct.
125+
126+ The asymmetry is real and reachable. ` WurstTypeIntLiteral ` is a proper subtype of both int and
127+ real, and ` caseMathOperation ` collapses two literals to int precisely so ` real r = 1 + 1 ` is an
128+ error. ` div ` /` mod ` return ` leftType ` , so ` real r = 7 div 2 ` compiles. Changing that made exactly
129+ one test fail — ` OptimizerTests.realFormatting_consistent_fromIntOps ` , which opens with
130+ ` real a = 1 div 2 ` — and AGENTS.md says the existing suite is the authoritative definition of
131+ behaviour. Real maps will contain the same shape.
132+
133+ So the question is the owner's: is ` real r = 7 div 2 ` meant to compile? If yes, the branch in
134+ ` AttrExprType ` wants a comment saying so, and this item closes. If no, it is a deliberate
135+ breaking change that needs the changelog, and ` realFormatting_consistent_fromIntOps ` needs
136+ rewriting to say what it actually tests, which is real formatting rather than that assignment.
137+ ` ExpressionTests.integerDivisionOfLiteralsIsStillAssignableToReal ` pins the behaviour meanwhile,
138+ so whichever way it goes is deliberate rather than accidental.
139+
129140- ** Eliminating the remaining ` castTo int ` .** The motivating case is timer data attachment
130141 (` ClosureTimers.wurst ` ), and the containers behind it: ` Table ` has 81 casts, ` HashList ` 13,
131142 ` HashSet ` 6, ` HashMap ` 4. None can adopt bounds as things stand, because an instance is
@@ -142,11 +153,18 @@ because `LOOP.md` refers to items by number.
142153
143154## Done
144155
145- - 8 . ` div ` and ` mod ` return int rather than the left operand's type, matching ` caseMathOperation ` .
146- Reachable, not harmless: an integer literal is a proper subtype of both int and real, and
147- addition collapses two of them to int precisely so ` real r = 1 + 1 ` stays an error — returning
148- ` leftType ` skipped that, so ` real r = 7 div 2 ` was accepted. Three tests in ` ExpressionTests ` :
149- both operators rejected against a real, and both still int.
156+ - 18 . Comparing an unresolved type parameter default is now an error rather than a quiet "not
157+ equal". Item 16 closed the path that reached a program, but the stand-in is produced by a static
158+ attribute and could surface anywhere, so the silence was the part worth removing. The whole suite
159+ is green with it throwing, which says nothing reachable produces one any more — and if something
160+ starts to, it says so instead of returning a wrong answer.
161+ - 16 . A never-written slot of a ` T array ` reads as the default of what T stands for. The default
162+ is computed by a static attribute, which cannot see the frames that know the type argument, so
163+ it produced a stand-in that compares equal only to another stand-in — ` Box<int>.first() == 0 `
164+ was quietly false on the interpreter while both backends had it right. ` ProgramState ` does know
165+ the substitution, so the stand-in is now resolved where the value is produced, at the array read
166+ and the member read, rather than at the comparison where the symptom shows. Item 18 covers the
167+ paths that could still leak one.
150168- 17 . A failing Lua test says so. ` translateAndTestLua ` now sets the environment label instead of
151169 reporting under whatever Jass configuration ran last.
152170- 5 (+ the part of 9 that follows it). A type class bound now dispatches from inside a closure on
@@ -204,6 +222,12 @@ because `LOOP.md` refers to items by number.
204222- ` % ` is real modulo in Wurst; ` mod ` is integer modulo. ` int % 8 ` types as ` real ` .
205223- Emitted Lua must be byte-identical for identical input (AGENTS.md §8). It is the only
206224 emitted output that can be diffed across runs — see item 11.
225+ - Two of this run's reverts were the same mistake: a name that looks redundant is usually carrying
226+ a distinction. The mangled method name separates overloads; ` leftType ` on ` div ` keeps a literal
227+ assignable to a real. Check what a name distinguishes before replacing it with a tidier one.
228+ - The suite is the specification. Before changing what the type checker accepts, grep the tests for
229+ the shape being rejected — item 8 looked like an oversight until one optimizer test turned out to
230+ depend on it.
207231- A test that hangs looks exactly like a test that is slow. If the suite stops making progress,
208232 take a thread dump of the forked worker (` jstack <pid> ` ) before killing it — it names the line.
209233- Method names are not what the frontend called them. ` LuaDispatchPreparation ` renames a whole
0 commit comments