Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public class EliminateGenerics {
private final Table<ImClass, GenericTypes, ImClass> specializedClasses = HashBasedTable.create();
private final Multimap<ImClass, BiConsumer<GenericTypes, ImClass>> onSpecializedClassTriggers = HashMultimap.create();

// Track concrete generic arguments for specialized functions to simplify later lookups
private final Map<ImFunction, GenericTypes> specializedFunctionGenerics = new IdentityHashMap<>();

// NEW: Track specialized global variables for generic static fields
// Key: (original generic global var, concrete type instantiation) -> specialized var
private final Table<ImVar, GenericTypes, ImVar> specializedGlobals = HashBasedTable.create();
Expand Down Expand Up @@ -205,15 +208,6 @@ private void removeGenericConstructs() {
for (ImClass c : prog.getClasses()) {
c.getFields().removeIf(f -> isGenericType(f.getType()));
Comment on lines 208 to 209

Choose a reason for hiding this comment

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

P1 Badge Generic globals remain in output

After specialization we still retain the original generic static fields in prog.getGlobals(): removeGenericConstructs now only strips generic functions/methods/classes and generic fields, but no longer removes the globals identified in identifyGenericGlobals. Because collectGenericUsages.visit(ImVar) explicitly skips globals, those variables keep their ImTypeVarRefs and nothing rewrites or replaces them. Any generic class/module with static thistype fields (e.g. the new linked list module) will therefore leave an unspecialized global definition in the final program, reintroducing type variables into the backend output.

Useful? React with 👍 / 👎.

}

// NEW: Remove original generic global variables
prog.getGlobals().removeIf(v -> {
if (globalToClass.containsKey(v)) {
WLogger.info("Removing generic global variable: " + v.getName() + " with type " + v.getType());
return true;
}
return false;
});
}

private void eliminateGenericUses() {
Expand Down Expand Up @@ -283,6 +277,7 @@ private ImFunction specializeFunction(ImFunction f, GenericTypes generics) {

ImFunction newF = f.copyWithRefs();
specializedFunctions.put(f, generics, newF);
specializedFunctionGenerics.put(newF, generics);
prog.getFunctions().add(newF);
newF.getTypeVariables().removeAll();
List<ImTypeVar> typeVars = f.getTypeVariables();
Expand Down Expand Up @@ -442,7 +437,7 @@ private ImClass specializeClass(ImClass c, GenericTypes generics) {
return specialized;
}
if (generics.containsTypeVariable()) {
throw new CompileError(c, "Generics should not contain type variables.");
throw new CompileError(c, "Generics should not contain type variables (" + c.getName() + " ⟪" + generics.makeName() + "⟫).");
}
ImClass newC = c.copyWithRefs();
newC.setSuperClasses(new ArrayList<>(newC.getSuperClasses()));
Expand Down Expand Up @@ -816,6 +811,22 @@ public void eliminate() {
ImVar f = ma.getVar();
ImClass owningClass = (ImClass) f.getParent().getParent();
GenericTypes generics = new GenericTypes(specializeTypeArgs(ma.getTypeArguments()));
// If the access still carries type variables, defer specialization until a concrete
// instantiation is created (e.g. when the surrounding generic function/class is
// specialized). If the receiver type is already concrete we can directly resolve the
// target field using that type information.
if (generics.containsTypeVariable()) {
ImType receiverType = specializeType(ma.getReceiver().attrTyp());
if (receiverType instanceof ImClassType) {
ImClass specializedClass = ((ImClassType) receiverType).getClassDef();
int fieldIndex = owningClass.getFields().indexOf(f);
ImVar newVar = specializedClass.getFields().get(fieldIndex);
ma.setVar(newVar);
ma.getTypeArguments().removeAll();
newVar.setType(specializeType(newVar.getType()));
}
return;
}
ImClass specializedClass = specializeClass(owningClass, generics);
int fieldIndex = owningClass.getFields().indexOf(f);
ImVar newVar = specializedClass.getFields().get(fieldIndex);
Expand Down Expand Up @@ -925,6 +936,11 @@ private GenericTypes inferGenericsFromFunction(Element element, ImClass owningCl
if (current instanceof ImFunction) {
ImFunction func = (ImFunction) current;

GenericTypes specialized = specializedFunctionGenerics.get(func);
if (specialized != null) {
return specialized;
}

// If function is still generic, we can't decide yet.
if (!func.getTypeVariables().isEmpty()) {
return null;
Expand Down Expand Up @@ -1073,7 +1089,22 @@ private ImType specializeType(ImType type) {
public ImType case_ImClassType(ImClassType t) {
ImTypeArguments typeArgs = t.getTypeArguments();
List<ImTypeArgument> newTypeArgs = specializeTypeArgs(typeArgs);
ImClass specializedClass = specializeClass(t.getClassDef(), new GenericTypes(newTypeArgs));
GenericTypes generics = new GenericTypes(newTypeArgs);

if (generics.containsTypeVariable()) {
Map<GenericTypes, ImClass> specialized = specializedClasses.row(t.getClassDef());

if (!specialized.isEmpty()) {
ImClass firstSpecialization = specialized.values().iterator().next();
return JassIm.ImClassType(firstSpecialization, JassIm.ImTypeArguments());
}

ImTypeArguments copiedArgs = JassIm.ImTypeArguments();
copiedArgs.addAll(newTypeArgs);
return JassIm.ImClassType(t.getClassDef(), copiedArgs);
}

ImClass specializedClass = specializeClass(t.getClassDef(), generics);
return JassIm.ImClassType(specializedClass, JassIm.ImTypeArguments());
}

Expand All @@ -1099,7 +1130,27 @@ class GenericReturnTypeFunc implements GenericUse {

@Override
public void eliminate() {
mc.setReturnType(specializeType(mc.getReturnType()));
ImType returnType = mc.getReturnType();

if (containsTypeVariable(returnType) && returnType instanceof ImClassType && !mc.getParameters().isEmpty()) {
ImClassType retClassType = (ImClassType) returnType;
ImType receiverType = mc.getParameters().get(0).getType();

if (receiverType instanceof ImClassType) {
ImClassType receiverClassType = (ImClassType) receiverType;
ImClassType adapted = adaptToSuperclass(receiverClassType, retClassType.getClassDef());

if (adapted != null) {
GenericTypes concrete = new GenericTypes(specializeTypeArgs(adapted.getTypeArguments()));
ImType specialized = ImAttrType.substituteType(returnType, concrete.getTypeArguments(), retClassType.getClassDef().getTypeVariables());

mc.setReturnType(specializeType(specialized));
return;
}
}
}

mc.setReturnType(specializeType(returnType));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1843,5 +1843,85 @@ public void arrayListInClosure() {
);
}

@Test
public void linkedListModule() {
testAssertOkLines(true,
"package test",
"native testSuccess()",
"module LinkedListModule",
" static thistype first = null",
" static thistype last = null",
" static int size = 0",
" thistype prev",
" thistype next",
" construct()",
" size++",
" if size == 1",
" first = this",
" prev = null",
" else",
" prev = last",
" last.next = this",
" first.prev = this",
" next = null",
" last = this",
" static function getFirst() returns thistype",
" return first",
" function getNext() returns thistype",
" if next == null",
" return first",
" return next",
" function getPrev() returns thistype",
" if prev == null",
" return last",
" return prev",
" function remove()",
" size--",
" if this != first",
" prev.next = next",
" else",
" first = next",
" if this != last",
" next.prev = prev",
" else",
" last = prev",
" ondestroy",
" remove()",
"class Node<T:>",
" use LinkedListModule",
"init",
" let a = new Node<int>",
" let b = new Node<int>",
" let c = new Node<int>",
" // simple sanity check: circular next traversal should loop",
" if a.getNext() != null and a.getPrev() != null",
" testSuccess()",
"endpackage"
);
}


@Test
public void genericClassWithLLModule() {
testAssertOkLinesWithStdLib(true,
"package test",
"import LinkedListModule",
"class Box<T:>",
" use LinkedListModule",
" private T value",
" function setValue(T v)",
" value = v",
" function getValue() returns T",
" return value",
"init",
" let b = new Box<int>",
" b.setValue(42)",
" if b.getValue() == 42 and b.prev == null",
" testSuccess()",
"endpackage"
);
}



}
Loading