From 26d4a38ca52779069b4277a948fcbf9b4457fdf5 Mon Sep 17 00:00:00 2001 From: Frotty Date: Mon, 1 Dec 2025 17:02:09 +0100 Subject: [PATCH 1/3] Update GenericsWithTypeclassesTests.java --- .../tests/GenericsWithTypeclassesTests.java | 310 ++++++++++++++++++ 1 file changed, 310 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 58879e31d..557ffd2fb 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 @@ -1531,5 +1531,315 @@ public void mixingLegacyOwner_newType_insideGenericClassMethod() { ); } + @Test + public void arrayListInClosure() { + testAssertOkLinesWithStdLib(true, + "package Hello", + "import NoWurst", + "import Integer", + "import Printing", + "", + "native testSuccess()", + "", + "public class ArrayList", + " private static T array store", + " private static int nextFreeIndex = 0", + "", + " // Memory management structures", + " 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", + "", + " /** Creates a new empty list with default capacity (16) */", + " construct()", + " allocateStorage(INITIAL_CAPACITY)", + "", + " /** Creates a new list with specified initial capacity - RECOMMENDED for performance */", + " construct(int initialCapacity)", + " allocateStorage(initialCapacity)", + "", + " /** Allocates storage section - tries to reuse freed sections first */", + " private function allocateStorage(int cap)", + " // Try to find a freed section that fits", + " for i = 0 to freeSectionCount - 1", + " if freeSectionCapacity[i] >= cap", + " startIndex = freeSectionStart[i]", + " capacity = freeSectionCapacity[i]", + "", + " // Remove this section from free list", + " for j = i to freeSectionCount - 2", + " freeSectionStart[j] = freeSectionStart[j + 1]", + " freeSectionCapacity[j] = freeSectionCapacity[j + 1]", + " freeSectionCount--", + " return", + "", + " // No suitable free section, allocate new", + " if nextFreeIndex + cap > JASS_MAX_ARRAY_SIZE", + " // Try to compact free sections", + " compactFreeList()", + "", + " if nextFreeIndex + cap > JASS_MAX_ARRAY_SIZE", + " // Still not enough, wrap around (dangerous!)", + " print(\"ArrayList: WARNING - Memory store exhausted, wrapping around!\")", + " nextFreeIndex = 0", + "", + " startIndex = nextFreeIndex", + " capacity = cap", + " nextFreeIndex += cap", + "", + " /** Compacts the free list by merging adjacent sections */", + " private static function compactFreeList()", + " if freeSectionCount <= 1", + " return", + "", + " // Sort free sections by start index using insertion sort", + " 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", + "", + " // Merge adjacent sections", + " var writeIdx = 0", + " for readIdx = 0 to freeSectionCount - 1", + " if writeIdx > 0 and freeSectionStart[writeIdx - 1] + freeSectionCapacity[writeIdx - 1] == freeSectionStart[readIdx]", + " // Merge with previous", + " freeSectionCapacity[writeIdx - 1] += freeSectionCapacity[readIdx]", + " else", + " // Keep as separate section", + " if writeIdx != readIdx", + " freeSectionStart[writeIdx] = freeSectionStart[readIdx]", + " freeSectionCapacity[writeIdx] = freeSectionCapacity[readIdx]", + " writeIdx++", + "", + " freeSectionCount = writeIdx", + "", + " // Update nextFreeIndex if last section extends to it", + " if freeSectionCount > 0", + " let lastIdx = freeSectionCount - 1", + " if freeSectionStart[lastIdx] + freeSectionCapacity[lastIdx] == nextFreeIndex", + " nextFreeIndex = freeSectionStart[lastIdx]", + " freeSectionCount--", + "", + " /** Frees this list's storage section for reuse */", + " private function freeStorage()", + " if capacity <= 0", + " return", + "", + " // Add to free list if there's space", + " if freeSectionCount < MAX_FREE_SECTIONS", + " freeSectionStart[freeSectionCount] = startIndex", + " freeSectionCapacity[freeSectionCount] = capacity", + " freeSectionCount++", + "", + " // If this was at the end, we can reclaim it immediately", + " if startIndex + capacity == nextFreeIndex", + " nextFreeIndex = startIndex", + " freeSectionCount--", + " else", + " // Free list full, try to compact", + " compactFreeList()", + "", + " // Try again after compaction", + " if freeSectionCount < MAX_FREE_SECTIONS", + " freeSectionStart[freeSectionCount] = startIndex", + " freeSectionCapacity[freeSectionCount] = capacity", + " freeSectionCount++", + "", + " /** Grows the capacity (doubles it) - EXPENSIVE OPERATION! */", + " private function grow()", + " let newCapacity = capacity * 2", + " let oldStart = startIndex", + " let oldCapacity = capacity", + "", + " // Try to allocate new section", + " allocateStorage(newCapacity)", + "", + " // Copy elements to new location", + " for i = 0 to size - 1", + " store[startIndex + i] = store[oldStart + i]", + "", + " // Free old section", + " let tempStart = startIndex", + " let tempCap = capacity", + " startIndex = oldStart", + " capacity = oldCapacity", + " freeStorage()", + " startIndex = tempStart", + " capacity = tempCap", + "", + " ondestroy", + " // Clear references to allow garbage collection", + " for i = 0 to size - 1", + " store[startIndex + i] = null", + "", + " // Return storage to free pool", + " freeStorage()", + "", + " /** Debug function to get memory layout info */", + " function getMemoryInfo() returns string", + " return \"Start: \" + startIndex.toString() + \", Capacity: \" + capacity.toString() + \", Size: \" + size.toString()", + "", + " /** Static function to get global memory state */", + " static function getGlobalMemoryInfo() returns string", + " return \"NextFree: \" + nextFreeIndex.toString() + \", FreeSections: \" + freeSectionCount.toString() + \", Used: \" + (nextFreeIndex - freeSectionCount).toString()", + "", + " // ============================================================================", + " // BASIC OPERATIONS", + " // ============================================================================", + "", + " /** 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", + " print(\"ArrayList: Index out of bounds: \" + index.toString())", + " return store[startIndex + index]", + "", + " /** Sets the element at the specified index (O(1)) */", + " function set(int index, T elem)", + " if index < 0 or index >= size", + " print(\"ArrayList: Index out of bounds: \" + index.toString())", + " store[startIndex + index] = elem", + "", + " /** Returns the index of the specified element or -1 if it doesn't exist (O(n)) */", + " function indexOf(T elem) returns int", + " for i = 0 to size - 1", + " if store[startIndex + i] == elem", + " return i", + " return -1", + "", + " /** Returns whether the list contains the specified element (O(n)) */", + " function has(T elem) returns boolean", + " return indexOf(elem) >= 0", + "", + " /** 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", + " print(\"ArrayList: Index out of bounds: \" + index.toString())", + "", + " let elem = store[startIndex + index]", + "", + " // Shift elements left", + " for i = index to size - 2", + " store[startIndex + i] = store[startIndex + i + 1]", + "", + " size--", + " return elem", + "", + " /** Removes the element at the given index by swapping with last element (O(1) - DOES NOT PRESERVE ORDER!) */", + " function removeSwap(int index) returns T", + " if index < 0 or index >= size", + " print(\"ArrayList: Index out of bounds: \" + index.toString())", + "", + " let elem = store[startIndex + index]", + "", + " // Replace with last element", + " size--", + " if index < size", + " store[startIndex + index] = store[startIndex + size]", + "", + " return elem", + "", + " /** Removes the first occurrence of the element from the list (O(n)) */", + " function remove(T elem) returns bool", + " let index = indexOf(elem)", + " if index >= 0", + " removeAt(index)", + " return true", + " return false", + "", + " /** 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()", + "" + ); + } + } From dc2f0943ea14740d44dfbb610ce75d950cfc42cd Mon Sep 17 00:00:00 2001 From: Frotty Date: Mon, 1 Dec 2025 21:44:15 +0100 Subject: [PATCH 2/3] remove cache redirector urls --- de.peeeq.wurstscript/settings.gradle | 3 - .../tests/GenericsWithTypeclassesTests.java | 76 ------------------- 2 files changed, 79 deletions(-) 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/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java index c0db13ee7..0f1bd021e 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 @@ -1628,7 +1628,6 @@ public void arrayListInClosure() { " private static T array store", " private static int nextFreeIndex = 0", "", - " // Memory management structures", " private static constant int MAX_FREE_SECTIONS = 256", " private static int array freeSectionStart", " private static int array freeSectionCapacity", @@ -1642,36 +1641,28 @@ public void arrayListInClosure() { " static function getNextFreeIndex() returns int", " return nextFreeIndex", "", - " /** Creates a new empty list with default capacity (16) */", " construct()", " allocateStorage(INITIAL_CAPACITY)", "", - " /** Creates a new list with specified initial capacity - RECOMMENDED for performance */", " construct(int initialCapacity)", " allocateStorage(initialCapacity)", "", - " /** Allocates storage section - tries to reuse freed sections first */", " private function allocateStorage(int cap)", - " // Try to find a freed section that fits", " for i = 0 to freeSectionCount - 1", " if freeSectionCapacity[i] >= cap", " startIndex = freeSectionStart[i]", " capacity = freeSectionCapacity[i]", "", - " // Remove this section from free list", " for j = i to freeSectionCount - 2", " freeSectionStart[j] = freeSectionStart[j + 1]", " freeSectionCapacity[j] = freeSectionCapacity[j + 1]", " freeSectionCount--", " return", "", - " // No suitable free section, allocate new", " if nextFreeIndex + cap > JASS_MAX_ARRAY_SIZE", - " // Try to compact free sections", " compactFreeList()", "", " if nextFreeIndex + cap > JASS_MAX_ARRAY_SIZE", - " // Still not enough, wrap around (dangerous!)", " print(\"ArrayList: WARNING - Memory store exhausted, wrapping around!\")", " nextFreeIndex = 0", "", @@ -1679,12 +1670,10 @@ public void arrayListInClosure() { " capacity = cap", " nextFreeIndex += cap", "", - " /** Compacts the free list by merging adjacent sections */", " private static function compactFreeList()", " if freeSectionCount <= 1", " return", "", - " // Sort free sections by start index using insertion sort", " for i = 1 to freeSectionCount - 1", " let keyStart = freeSectionStart[i]", " let keyCap = freeSectionCapacity[i]", @@ -1698,14 +1687,11 @@ public void arrayListInClosure() { " freeSectionStart[j + 1] = keyStart", " freeSectionCapacity[j + 1] = keyCap", "", - " // Merge adjacent sections", " var writeIdx = 0", " for readIdx = 0 to freeSectionCount - 1", " if writeIdx > 0 and freeSectionStart[writeIdx - 1] + freeSectionCapacity[writeIdx - 1] == freeSectionStart[readIdx]", - " // Merge with previous", " freeSectionCapacity[writeIdx - 1] += freeSectionCapacity[readIdx]", " else", - " // Keep as separate section", " if writeIdx != readIdx", " freeSectionStart[writeIdx] = freeSectionStart[readIdx]", " freeSectionCapacity[writeIdx] = freeSectionCapacity[readIdx]", @@ -1713,52 +1699,42 @@ public void arrayListInClosure() { "", " freeSectionCount = writeIdx", "", - " // Update nextFreeIndex if last section extends to it", " if freeSectionCount > 0", " let lastIdx = freeSectionCount - 1", " if freeSectionStart[lastIdx] + freeSectionCapacity[lastIdx] == nextFreeIndex", " nextFreeIndex = freeSectionStart[lastIdx]", " freeSectionCount--", "", - " /** Frees this list's storage section for reuse */", " private function freeStorage()", " if capacity <= 0", " return", "", - " // Add to free list if there's space", " if freeSectionCount < MAX_FREE_SECTIONS", " freeSectionStart[freeSectionCount] = startIndex", " freeSectionCapacity[freeSectionCount] = capacity", " freeSectionCount++", "", - " // If this was at the end, we can reclaim it immediately", " if startIndex + capacity == nextFreeIndex", " nextFreeIndex = startIndex", " freeSectionCount--", " else", - " // Free list full, try to compact", " compactFreeList()", "", - " // Try again after compaction", " if freeSectionCount < MAX_FREE_SECTIONS", " freeSectionStart[freeSectionCount] = startIndex", " freeSectionCapacity[freeSectionCount] = capacity", " freeSectionCount++", "", - " /** Grows the capacity (doubles it) - EXPENSIVE OPERATION! */", " private function grow()", " let newCapacity = capacity * 2", " let oldStart = startIndex", " let oldCapacity = capacity", "", - " // Try to allocate new section", " allocateStorage(newCapacity)", "", - " // Copy elements to new location", " for i = 0 to size - 1", " store[startIndex + i] = store[oldStart + i]", "", - " // Free old section", " let tempStart = startIndex", " let tempCap = capacity", " startIndex = oldStart", @@ -1768,25 +1744,12 @@ public void arrayListInClosure() { " capacity = tempCap", "", " ondestroy", - " // Clear references to allow garbage collection", " for i = 0 to size - 1", " store[startIndex + i] = null", "", " // Return storage to free pool", " freeStorage()", "", - " /** Debug function to get memory layout info */", - " function getMemoryInfo() returns string", - " return \"Start: \" + startIndex.toString() + \", Capacity: \" + capacity.toString() + \", Size: \" + size.toString()", - "", - " /** Static function to get global memory state */", - " static function getGlobalMemoryInfo() returns string", - " return \"NextFree: \" + nextFreeIndex.toString() + \", FreeSections: \" + freeSectionCount.toString() + \", Used: \" + (nextFreeIndex - freeSectionCount).toString()", - "", - " // ============================================================================", - " // BASIC OPERATIONS", - " // ============================================================================", - "", " /** Adds one or more elements to the end of the list (amortized O(1)) */", " function add(vararg T elems)", " for elem in elems", @@ -1801,23 +1764,6 @@ public void arrayListInClosure() { " print(\"ArrayList: Index out of bounds: \" + index.toString())", " return store[startIndex + index]", "", - " /** Sets the element at the specified index (O(1)) */", - " function set(int index, T elem)", - " if index < 0 or index >= size", - " print(\"ArrayList: Index out of bounds: \" + index.toString())", - " store[startIndex + index] = elem", - "", - " /** Returns the index of the specified element or -1 if it doesn't exist (O(n)) */", - " function indexOf(T elem) returns int", - " for i = 0 to size - 1", - " if store[startIndex + i] == elem", - " return i", - " return -1", - "", - " /** Returns whether the list contains the specified element (O(n)) */", - " function has(T elem) returns boolean", - " return indexOf(elem) >= 0", - "", " /** 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", @@ -1832,28 +1778,6 @@ public void arrayListInClosure() { " size--", " return elem", "", - " /** Removes the element at the given index by swapping with last element (O(1) - DOES NOT PRESERVE ORDER!) */", - " function removeSwap(int index) returns T", - " if index < 0 or index >= size", - " print(\"ArrayList: Index out of bounds: \" + index.toString())", - "", - " let elem = store[startIndex + index]", - "", - " // Replace with last element", - " size--", - " if index < size", - " store[startIndex + index] = store[startIndex + size]", - "", - " return elem", - "", - " /** Removes the first occurrence of the element from the list (O(n)) */", - " function remove(T elem) returns bool", - " let index = indexOf(elem)", - " if index >= 0", - " removeAt(index)", - " return true", - " return false", - "", " /** Returns the size of the list (O(1)) */", " function size() returns int", " return size", From 5b9ffa6616a5347d39b48d184bc683f7ce20b0ec Mon Sep 17 00:00:00 2001 From: Frotty Date: Tue, 2 Dec 2025 00:08:16 +0100 Subject: [PATCH 3/3] fix callees in specialized functions --- .../imtranslation/EliminateGenerics.java | 49 +++++++++++++++++++ .../tests/GenericsWithTypeclassesTests.java | 11 ++--- 2 files changed, 52 insertions(+), 8 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 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 0f1bd021e..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 @@ -1616,11 +1616,9 @@ public void inheritedField_lazyClosure_uses_enclosing_receiver() { @Test public void arrayListInClosure() { - testAssertOkLinesWithStdLib(true, + testAssertOkLines(true, "package Hello", "import NoWurst", - "import Integer", - "import Printing", "", "native testSuccess()", "", @@ -1659,11 +1657,10 @@ public void arrayListInClosure() { " freeSectionCount--", " return", "", - " if nextFreeIndex + cap > JASS_MAX_ARRAY_SIZE", + " if nextFreeIndex + cap > 10000", " compactFreeList()", "", - " if nextFreeIndex + cap > JASS_MAX_ARRAY_SIZE", - " print(\"ArrayList: WARNING - Memory store exhausted, wrapping around!\")", + " if nextFreeIndex + cap > 10000", " nextFreeIndex = 0", "", " startIndex = nextFreeIndex", @@ -1761,13 +1758,11 @@ public void arrayListInClosure() { " /** Returns the element at the specified index (O(1)) */", " function get(int index) returns T", " if index < 0 or index >= size", - " print(\"ArrayList: Index out of bounds: \" + index.toString())", " 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", - " print(\"ArrayList: Index out of bounds: \" + index.toString())", "", " let elem = store[startIndex + index]", "",