diff --git a/de.peeeq.wurstscript/settings.gradle b/de.peeeq.wurstscript/settings.gradle index febed67c6..5ec2363ae 100644 --- a/de.peeeq.wurstscript/settings.gradle +++ b/de.peeeq.wurstscript/settings.gradle @@ -1,9 +1,7 @@ pluginManagement { repositories { - maven { url = uri("https://cache-redirector.jetbrains.com/plugins.gradle.org/m2") } gradlePluginPortal() maven { url = uri("https://plugins.gradle.org/m2") } - maven { url = uri("https://cache-redirector.jetbrains.com/maven-central") } mavenCentral() } } @@ -11,7 +9,6 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) repositories { - maven { url = uri("https://cache-redirector.jetbrains.com/maven-central") } mavenCentral() google() maven { url 'https://jitpack.io' } 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 10e9218f0..af579b99a 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 @@ -223,6 +223,49 @@ private void eliminateGenericUses() { } } + private void fixCalleesInSpecializedFunction(ImFunction newF, GenericTypes generics) { + newF.accept(new Element.DefaultVisitor() { + + @Override + public void visit(ImFunctionCall fc) { + super.visit(fc); + + ImFunction callee = fc.getFunc(); + if (callee == null) return; + + // Only interesting if callee itself is generic + if (callee.getTypeVariables().isEmpty()) { + return; + } + + // Determine which generics to use for the callee + GenericTypes calleeGenerics; + + if (!fc.getTypeArguments().isEmpty()) { + // Call carries explicit type args → honor them + calleeGenerics = new GenericTypes(specializeTypeArgs(fc.getTypeArguments())); + } else { + // No explicit type args → use the same generics context as the enclosing function. + // This matches the pattern: destroyArrayList(this: ArrayList) calls ArrayList_onDestroy(this) + calleeGenerics = generics; + } + + if (calleeGenerics.containsTypeVariable()) { + // Still not concrete → let the normal pipeline handle it later or fail explicitly if needed + return; + } + + ImFunction specializedCallee = specializedFunctions.get(callee, calleeGenerics); + if (specializedCallee == null) { + specializedCallee = specializeFunction(callee, calleeGenerics); + } + + fc.setFunc(specializedCallee); + fc.getTypeArguments().removeAll(); + } + }); + } + /** * creates a specialized version of this function */ @@ -246,7 +289,13 @@ private ImFunction specializeFunction(ImFunction f, GenericTypes generics) { newF.setName(f.getName() + "⟪" + generics.makeName() + "⟫"); rewriteGenerics(newF, generics, typeVars); + + // fix calls inside this specialized function so they also point to specialized callees + fixCalleesInSpecializedFunction(newF, generics); + + // Then collect further generic uses inside the now-specialized body collectGenericUsages(newF); + return newF; } 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 b8a8cfa8e..571105e7e 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 @@ -1614,5 +1614,234 @@ public void inheritedField_lazyClosure_uses_enclosing_receiver() { ); } + @Test + public void arrayListInClosure() { + testAssertOkLines(true, + "package Hello", + "import NoWurst", + "", + "native testSuccess()", + "", + "public class ArrayList", + " private static T array store", + " private static int nextFreeIndex = 0", + "", + " private static constant int MAX_FREE_SECTIONS = 256", + " private static int array freeSectionStart", + " private static int array freeSectionCapacity", + " private static int freeSectionCount = 0", + "", + " private int startIndex", + " private int capacity", + " private int size = 0", + " private static constant int INITIAL_CAPACITY = 16", + "", + " static function getNextFreeIndex() returns int", + " return nextFreeIndex", + "", + " construct()", + " allocateStorage(INITIAL_CAPACITY)", + "", + " construct(int initialCapacity)", + " allocateStorage(initialCapacity)", + "", + " private function allocateStorage(int cap)", + " for i = 0 to freeSectionCount - 1", + " if freeSectionCapacity[i] >= cap", + " startIndex = freeSectionStart[i]", + " capacity = freeSectionCapacity[i]", + "", + " for j = i to freeSectionCount - 2", + " freeSectionStart[j] = freeSectionStart[j + 1]", + " freeSectionCapacity[j] = freeSectionCapacity[j + 1]", + " freeSectionCount--", + " return", + "", + " if nextFreeIndex + cap > 10000", + " compactFreeList()", + "", + " if nextFreeIndex + cap > 10000", + " nextFreeIndex = 0", + "", + " startIndex = nextFreeIndex", + " capacity = cap", + " nextFreeIndex += cap", + "", + " private static function compactFreeList()", + " if freeSectionCount <= 1", + " return", + "", + " for i = 1 to freeSectionCount - 1", + " let keyStart = freeSectionStart[i]", + " let keyCap = freeSectionCapacity[i]", + " var j = i - 1", + "", + " while j >= 0 and freeSectionStart[j] > keyStart", + " freeSectionStart[j + 1] = freeSectionStart[j]", + " freeSectionCapacity[j + 1] = freeSectionCapacity[j]", + " j--", + "", + " freeSectionStart[j + 1] = keyStart", + " freeSectionCapacity[j + 1] = keyCap", + "", + " var writeIdx = 0", + " for readIdx = 0 to freeSectionCount - 1", + " if writeIdx > 0 and freeSectionStart[writeIdx - 1] + freeSectionCapacity[writeIdx - 1] == freeSectionStart[readIdx]", + " freeSectionCapacity[writeIdx - 1] += freeSectionCapacity[readIdx]", + " else", + " if writeIdx != readIdx", + " freeSectionStart[writeIdx] = freeSectionStart[readIdx]", + " freeSectionCapacity[writeIdx] = freeSectionCapacity[readIdx]", + " writeIdx++", + "", + " freeSectionCount = writeIdx", + "", + " if freeSectionCount > 0", + " let lastIdx = freeSectionCount - 1", + " if freeSectionStart[lastIdx] + freeSectionCapacity[lastIdx] == nextFreeIndex", + " nextFreeIndex = freeSectionStart[lastIdx]", + " freeSectionCount--", + "", + " private function freeStorage()", + " if capacity <= 0", + " return", + "", + " if freeSectionCount < MAX_FREE_SECTIONS", + " freeSectionStart[freeSectionCount] = startIndex", + " freeSectionCapacity[freeSectionCount] = capacity", + " freeSectionCount++", + "", + " if startIndex + capacity == nextFreeIndex", + " nextFreeIndex = startIndex", + " freeSectionCount--", + " else", + " compactFreeList()", + "", + " if freeSectionCount < MAX_FREE_SECTIONS", + " freeSectionStart[freeSectionCount] = startIndex", + " freeSectionCapacity[freeSectionCount] = capacity", + " freeSectionCount++", + "", + " private function grow()", + " let newCapacity = capacity * 2", + " let oldStart = startIndex", + " let oldCapacity = capacity", + "", + " allocateStorage(newCapacity)", + "", + " for i = 0 to size - 1", + " store[startIndex + i] = store[oldStart + i]", + "", + " let tempStart = startIndex", + " let tempCap = capacity", + " startIndex = oldStart", + " capacity = oldCapacity", + " freeStorage()", + " startIndex = tempStart", + " capacity = tempCap", + "", + " ondestroy", + " for i = 0 to size - 1", + " store[startIndex + i] = null", + "", + " // Return storage to free pool", + " freeStorage()", + "", + " /** Adds one or more elements to the end of the list (amortized O(1)) */", + " function add(vararg T elems)", + " for elem in elems", + " if size >= capacity", + " grow()", + " store[startIndex + size] = elem", + " size++", + "", + " /** Returns the element at the specified index (O(1)) */", + " function get(int index) returns T", + " if index < 0 or index >= size", + " return store[startIndex + index]", + "", + " /** Removes the element at the given index and returns it (O(n) - shifts elements) */", + " function removeAt(int index) returns T", + " if index < 0 or index >= size", + "", + " let elem = store[startIndex + index]", + "", + " // Shift elements left", + " for i = index to size - 2", + " store[startIndex + i] = store[startIndex + i + 1]", + "", + " size--", + " return elem", + "", + " /** Returns the size of the list (O(1)) */", + " function size() returns int", + " return size", + "", + " /** Checks whether this list is empty (O(1)) */", + " function isEmpty() returns boolean", + " return size == 0", + "", + "", + "public function lazy(Lazy l) returns Lazy", + " return l", + "", + "public abstract class Lazy", + " T val = null", + " var wasRetrieved = false", + "", + " abstract function retrieve() returns T", + "", + " function get() returns T", + " if not wasRetrieved", + " val = retrieve()", + " wasRetrieved = true", + " return val", + "", + "public class CFBuilding", + " CFBuilding precursor = null", + " ArrayList upgrades = null", + "", + " Lazy hasAAUpgrade = lazy(() -> begin", + " var result = false", + " if upgrades != null", + " result = true", + " // perform iterative search through \"tree\"", + " var toCheck = new ArrayList()", + " // toCheck.addAll(upgrades)", + " while not toCheck.isEmpty()", + " let b = toCheck.removeAt(0)", + " if b.isAntiAir", + " result = true", + " break", + " if b.upgrades != null", + " // toCheck.addAll(b.upgrades)", + "", + " destroy toCheck", + " return result", + " end)", + "", + " var netWorthDiv100 = lazy(() -> begin", + " var worth = 0", + " var cur = this", + " while cur != null", + " worth += cur.goldCost", + " cur = cur.precursor", + " return worth / 200.", + " end)", + "", + " var goldCost = 0", + " var isAntiAir = false // Is the building an anti air unit", + "", + "init", + " let b = new CFBuilding()", + " b.upgrades = new ArrayList()", + " let aa = b", + " .hasAAUpgrade.get()", + " if aa == true", + " testSuccess()", + "" + ); + } + }