From 10387c16554869cb692afef27c3f191a19dee1e4 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 11 Dec 2025 14:53:36 +0100 Subject: [PATCH 1/2] Update GenericsWithTypeclassesTests.java --- .../tests/GenericsWithTypeclassesTests.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java index 571105e7e..9e0fba820 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java @@ -1843,5 +1843,39 @@ public void arrayListInClosure() { ); } + @Test + public void genericClassWithModule() { + testAssertOkLines(true, + "package test", + "native testSuccess()", + "module M", + " 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", + " last.next = this", + "class Box", + " use M", + " private T value", + " function setValue(T v)", + " value = v", + " function getValue() returns T", + " return value", + "init", + " let b = new Box", + " b.setValue(42)", + " if b.getValue() == 42 and b.prev == null", + " testSuccess()", + "endpackage" + ); + } + } From d0831b3e4338696c2887e013e60fa27edd0e9193 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 11 Dec 2025 19:21:54 +0100 Subject: [PATCH 2/2] more generic fixes --- .../imtranslation/EliminateGenerics.java | 75 ++++++++++++++++--- .../tests/GenericsWithTypeclassesTests.java | 52 ++++++++++++- 2 files changed, 112 insertions(+), 15 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java index 130b2afa4..e0e4fcb2f 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java @@ -24,6 +24,9 @@ public class EliminateGenerics { private final Table specializedClasses = HashBasedTable.create(); private final Multimap> onSpecializedClassTriggers = HashMultimap.create(); + // Track concrete generic arguments for specialized functions to simplify later lookups + private final Map 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 specializedGlobals = HashBasedTable.create(); @@ -205,15 +208,6 @@ private void removeGenericConstructs() { for (ImClass c : prog.getClasses()) { c.getFields().removeIf(f -> isGenericType(f.getType())); } - - // 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() { @@ -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 typeVars = f.getTypeVariables(); @@ -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())); @@ -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); @@ -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; @@ -1073,7 +1089,22 @@ private ImType specializeType(ImType type) { public ImType case_ImClassType(ImClassType t) { ImTypeArguments typeArgs = t.getTypeArguments(); List newTypeArgs = specializeTypeArgs(typeArgs); - ImClass specializedClass = specializeClass(t.getClassDef(), new GenericTypes(newTypeArgs)); + GenericTypes generics = new GenericTypes(newTypeArgs); + + if (generics.containsTypeVariable()) { + Map 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()); } @@ -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)); } } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java index 9e0fba820..ad1bb0973 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java @@ -1844,11 +1844,11 @@ public void arrayListInClosure() { } @Test - public void genericClassWithModule() { + public void linkedListModule() { testAssertOkLines(true, "package test", "native testSuccess()", - "module M", + "module LinkedListModule", " static thistype first = null", " static thistype last = null", " static int size = 0", @@ -1860,9 +1860,54 @@ public void genericClassWithModule() { " 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", + " use LinkedListModule", + "init", + " let a = new Node", + " let b = new Node", + " let c = new Node", + " // 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", - " use M", + " use LinkedListModule", " private T value", " function setValue(T v)", " value = v", @@ -1878,4 +1923,5 @@ public void genericClassWithModule() { } + }