@@ -128,6 +128,41 @@ public void transformGenericNewOnly() {
128128 }
129129 eliminateRemainingGenericNewCalls ();
130130 assertNoReachableGenericNewMarkers ();
131+ settleRemainingDispatches ();
132+ }
133+
134+ /**
135+ * Neutralises the dispatches left in functions that were specialized.
136+ * <p>
137+ * Such a function is dead: every reachable call to it was rewritten to its specialization, so a
138+ * dispatch still sitting in the original can never run. This backend keeps generics rather than
139+ * removing them wholesale, so those originals are still translated and the dispatch would reach
140+ * a backend with no way to express it.
141+ * <p>
142+ * Anything else is left alone. A dispatch may legitimately remain in a bounded generic that is
143+ * merely declared and never called, and garbage removal deletes those later; deciding here
144+ * would mean duplicating reachability. One which survives that far and still reaches the
145+ * backend is reported there, where it is known to be both reachable and unresolvable.
146+ */
147+ private void settleRemainingDispatches () {
148+ List <ImTypeVarDispatch > remaining = new ArrayList <>();
149+ prog .accept (new Element .DefaultVisitor () {
150+ @ Override
151+ public void visit (ImTypeVarDispatch dispatch ) {
152+ super .visit (dispatch );
153+ remaining .add (dispatch );
154+ }
155+ });
156+ for (ImTypeVarDispatch dispatch : remaining ) {
157+ ImFunction owner = dispatch .getNearestFunc ();
158+ if (owner != null && specializedFunctions .containsRow (owner )) {
159+ dispatch .replaceBy (defaultValueFor (dispatch .getTypeClassFunc ().getReturnType ()));
160+ }
161+ }
162+ }
163+
164+ private static ImExpr defaultValueFor (ImType type ) {
165+ return JassIm .ImNull (type .copy ());
131166 }
132167
133168
@@ -271,7 +306,7 @@ public void visit(ImAlloc alloc) {
271306 // Constructing a class whose methods dispatch has to be specialised as well:
272307 // otherwise the constructor keeps a generic result type, and a method call on that
273308 // result never becomes concrete enough to resolve.
274- if (classNeedsSpecialization (alloc .getClazz ().getClassDef (), visitedFunctions , visitedMethods )) {
309+ if (classNeedsSpecialization (alloc .getClazz ().getClassDef ())) {
275310 found [0 ] = true ;
276311 return ;
277312 }
@@ -320,44 +355,59 @@ && functionNeedsSpecialization(method.getImplementation(), visitedFunctions, vis
320355 /**
321356 * Whether constructing this class requires the concrete type argument, because one of its own
322357 * or inherited members dispatches on a type class bound.
358+ * <p>
359+ * Deliberately a property of the class alone, not of the path that asked. An earlier version
360+ * threaded the caller's visited set through here and memoised the answer, so a query made while
361+ * one of the class's own functions was already being visited recorded a negative result that
362+ * then stood for every later query.
323363 */
324- private boolean classNeedsSpecialization (ImClass classDef , Set <ImFunction > visitedFunctions ,
325- Set <ImMethod > visitedMethods ) {
326- Boolean cached = classNeedsSpecialization .get (classDef );
364+ private boolean classNeedsSpecialization (ImClass classDef ) {
365+ Boolean cached = classNeedsSpecializationCache .get (classDef );
327366 if (cached != null ) {
328- // Already answered, or currently being answered: a class reached through its own
329- // members contributes nothing new to the decision.
330367 return cached ;
331368 }
332- classNeedsSpecialization .put (classDef , false );
333- boolean result = false ;
369+ classNeedsSpecializationCache .put (classDef , false );
370+ boolean result = classDispatchesOnBound (classDef ,
371+ Collections .newSetFromMap (new IdentityHashMap <>()));
372+ classNeedsSpecializationCache .put (classDef , result );
373+ return result ;
374+ }
375+
376+ private boolean classDispatchesOnBound (ImClass classDef , Set <ImClass > visited ) {
377+ if (!visited .add (classDef )) {
378+ return false ;
379+ }
334380 for (ImFunction f : classDef .getFunctions ()) {
335- if (functionNeedsSpecialization (f , visitedFunctions , visitedMethods )) {
336- result = true ;
337- break ;
381+ if (containsDispatch (f )) {
382+ return true ;
338383 }
339384 }
340- if (!result ) {
341- for (ImMethod m : classDef .getMethods ()) {
342- if (methodNeedsSpecialization (m , visitedFunctions , visitedMethods )) {
343- result = true ;
344- break ;
345- }
385+ for (ImMethod m : classDef .getMethods ()) {
386+ if (m .getImplementation () != null && containsDispatch (m .getImplementation ())) {
387+ return true ;
346388 }
347389 }
348- if (!result ) {
349- for (ImClassType superType : classDef .getSuperClasses ()) {
350- if (classNeedsSpecialization (superType .getClassDef (), visitedFunctions , visitedMethods )) {
351- result = true ;
352- break ;
353- }
390+ for (ImClassType superType : classDef .getSuperClasses ()) {
391+ if (classDispatchesOnBound (superType .getClassDef (), visited )) {
392+ return true ;
354393 }
355394 }
356- classNeedsSpecialization .put (classDef , result );
357- return result ;
395+ return false ;
396+ }
397+
398+ /** Whether this function body dispatches on a bound, without following calls out of it. */
399+ private static boolean containsDispatch (ImFunction f ) {
400+ boolean [] found = {false };
401+ f .accept (new Element .DefaultVisitor () {
402+ @ Override
403+ public void visit (ImTypeVarDispatch dispatch ) {
404+ found [0 ] = true ;
405+ }
406+ });
407+ return found [0 ];
358408 }
359409
360- private final Map <ImClass , Boolean > classNeedsSpecialization = new IdentityHashMap <>();
410+ private final Map <ImClass , Boolean > classNeedsSpecializationCache = new IdentityHashMap <>();
361411
362412 private void assertNoReachableGenericNewMarkers () {
363413 prog .accept (new Element .DefaultVisitor () {
0 commit comments