From 965b8a5be39390d688f1dd2b90b730970677b05a Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 11 Oct 2026 10:37:44 +0200 Subject: [PATCH 1/2] Lua dispatch: keep same-named classes of different packages in groups of their own A dispatch group's key named the class, the method and the types of its signature by name only, so two packages which each declare Box, First and Second shared one group for pick. The keys now end with the packages of the classes and type-variable owners they name, behind a separator which keeps the order of keys which differed before. --- .../imtranslation/LuaDispatchPreparation.java | 53 ++++++++++++++++- .../tests/DeterministicChecks.java | 57 +++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/LuaDispatchPreparation.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/LuaDispatchPreparation.java index 62d4fcb41..d956cc096 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/LuaDispatchPreparation.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/LuaDispatchPreparation.java @@ -5,10 +5,12 @@ import de.peeeq.wurstscript.ast.AstElementWithTypeParameters; import de.peeeq.wurstscript.ast.ExprClosure; import de.peeeq.wurstscript.ast.FuncDef; +import de.peeeq.wurstscript.ast.WPackage; import de.peeeq.wurstscript.jassIm.ImArrayType; import de.peeeq.wurstscript.jassIm.ImArrayTypeMulti; import de.peeeq.wurstscript.jassIm.ImClass; import de.peeeq.wurstscript.jassIm.ImClassType; +import de.peeeq.wurstscript.jassIm.Element; import de.peeeq.wurstscript.jassIm.ImFunction; import de.peeeq.wurstscript.jassIm.ImMethod; import de.peeeq.wurstscript.jassIm.ImProg; @@ -100,7 +102,10 @@ private static void assignDispatchGroupKeys(List allMethods) { if (partition.isEmpty()) { continue; } - String key = methodSortKey(partition.get(0)) + "|" + dispatchSignatureKey(partition.get(0)); + // The sort key names the class only; two packages may each declare one of that name. + ImClass owner = partition.get(0).attrClass(); + String key = methodSortKey(partition.get(0)) + "|" + dispatchSignatureKey(partition.get(0)) + + OWNERS + (owner == null ? "" : packageOf(owner.getTrace())); for (int k = 0; k < partition.size(); k++) { ImMethod method = partition.get(k); method.setLuaDispatchGroupKey(key); @@ -467,6 +472,11 @@ private static String dispatchSignatureKey(ImMethod method) { } sb.append(typeKey(params.get(i).getType())); } + sb.append(OWNERS); + appendOwners(implementation.getReturnType(), sb); + for (int i = 1; i < params.size(); i++) { + appendOwners(params.get(i).getType(), sb); + } return sb.toString(); } @@ -489,6 +499,10 @@ private static String dispatchParameterSignatureKey(ImMethod method) { } sb.append(typeKey(params.get(i).getType())); } + sb.append(OWNERS); + for (int i = 1; i < params.size(); i++) { + appendOwners(params.get(i).getType(), sb); + } return sb.toString(); } @@ -657,6 +671,43 @@ public static String typeKey(ImType type) { return type.toString(); } + /** + * Separates a signature from the packages of what it names. It sorts below every character a type key holds, so + * two signatures which differed before them keep their order, and two which named equal types stay together. + */ + private static final char OWNERS = ''; + + /** + * The packages of the classes and of the owners of the type variables {@code type} names, in order. Two packages may + * each declare a class of the same name, and {@link #typeKey} names a class by its name only: without these, the + * methods of two such classes shared a dispatch group and their signatures compared equal for an alias. + */ + private static void appendOwners(ImType type, StringBuilder sb) { + if (type instanceof ImTypeVarRef ref) { + ImTypeVar typeVar = ref.getTypeVariable(); + Element owner = typeVar.getParent() instanceof ImTypeVars vars ? vars.getParent() : null; + sb.append(owner instanceof ImClass c ? packageOf(c.getTrace()) + : owner instanceof ImFunction f ? packageOf(f.getTrace()) : "").append(';'); + } else if (type instanceof ImClassType classType) { + sb.append(packageOf(classType.getClassDef().getTrace())).append(';'); + for (int i = 0; i < classType.getTypeArguments().size(); i++) { + appendOwners(classType.getTypeArguments().get(i).getType(), sb); + } + } else if (type instanceof ImArrayType arrayType) { + appendOwners(arrayType.getEntryType(), sb); + } else if (type instanceof ImArrayTypeMulti arrayType) { + appendOwners(arrayType.getEntryType(), sb); + } else if (type instanceof ImTupleType tupleType) { + for (int i = 0; i < tupleType.getTypes().size(); i++) { + appendOwners(tupleType.getTypes().get(i), sb); + } + } + } + + private static String packageOf(de.peeeq.wurstscript.ast.Element trace) { + return trace != null && trace.attrNearestPackage() instanceof WPackage p ? p.getName() : ""; + } + private static String typeVarKey(ImTypeVar typeVar) { String owner = "?"; int position = -1; diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/DeterministicChecks.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/DeterministicChecks.java index 583797f62..339bd0b59 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/DeterministicChecks.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/DeterministicChecks.java @@ -841,6 +841,63 @@ public void sameNamedClassesInSeveralPackagesEmitTheSameLuaInAnyUnitOrder() thro "same-named receivers must bind in an order independent of the compilation units"); } + /** + * Two packages each declare the generic hierarchy Box, First and Second. The key of a dispatch group named its class, + * its method and the types of its signature by name only, so both families of pick had the same key and shared + * one group: the packages of the class and of what the signature names keep them apart, in any order of the + * compilation units. + */ + @Test + public void sameNamedGenericHierarchiesInTwoPackagesKeepTheirOwnDispatchGroups() throws IOException { + assertOwnDispatchGroups("sameNamedOldGenericHierarchies", "T"); + } + + /** The same with new generics, whose signatures name the type variable and so its owner. */ + @Test + public void sameNamedNewGenericHierarchiesInTwoPackagesKeepTheirOwnDispatchGroups() throws IOException { + assertOwnDispatchGroups("sameNamedNewGenericHierarchies", "T:"); + } + + private void assertOwnDispatchGroups(String name, String typeParameter) throws IOException { + List units = new ArrayList<>(); + for (String p : List.of("PA", "PB")) { + boolean first = p.equals("PA"); + units.add(compilationUnit(p + ".wurst", + "package " + p, + "public abstract class Box<" + typeParameter + ">", + " abstract function pick(T a, T b) returns T", + "public class First<" + typeParameter + "> extends Box", + " override function pick(T a, T b) returns T", + " return " + (first ? "a" : "b"), + "public class Second<" + typeParameter + "> extends Box", + " override function pick(T a, T b) returns T", + " return " + (first ? "b" : "a"), + "public function run" + p + "() returns int", + " Box x = new First()", + " Box y = new Second()", + " return x.pick(10, 20) * 100 + y.pick(10, 20)")); + } + units.add(compilationUnit("Main.wurst", + "package Main", + "import PA", + "import PB", + "native testSuccess()", + "init", + " if runPA() == 1020 and runPB() == 2010", + " testSuccess()")); + String lua = compileAndRunToLua(name, units.toArray(new CU[0])); + java.util.Set slots = new java.util.TreeSet<>(); + java.util.regex.Matcher binding = java.util.regex.Pattern.compile("\\n\\t\\w+\\.(\\w+) = First_First_pick\\w*\\n") + .matcher(lua); + while (binding.find()) { + slots.add(binding.group(1)); + } + assertEquals(slots.size(), 2, "each package's First binds pick to a slot of its own:\n" + lua); + List shuffled = List.of(units.get(2), units.get(1), units.get(0)); + assertEquals(compileToLua(name, shuffled), lua, + "the slots are the same in any order of the compilation units"); + } + /** Compiles {@code units} for every backend, runs them, and returns the Lua script written under {@code name}. */ private String compileAndRunToLua(String name, CU... units) throws IOException { testNamed(name).testLua(true).luaOnly(false).executeProg().compilationUnits(units); From 69eacbee0ff4aebd9d47a9f79ef9b2ee509a5d0e Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 11 Oct 2026 10:49:33 +0200 Subject: [PATCH 2/2] Test that same-named overrides in two packages share their root's slot --- .../tests/DeterministicChecks.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/DeterministicChecks.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/DeterministicChecks.java index 339bd0b59..980e1cdc0 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/DeterministicChecks.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/DeterministicChecks.java @@ -858,6 +858,52 @@ public void sameNamedNewGenericHierarchiesInTwoPackagesKeepTheirOwnDispatchGroup assertOwnDispatchGroups("sameNamedNewGenericHierarchies", "T:"); } + /** + * One family across packages: Box in a shared package, and a class First overriding pick in each of two others. + * Both overrides belong to Box's group and must bind the slot a call through Box uses. + */ + @Test + public void sameNamedOverridesInTwoPackagesShareTheirRootSlot() throws IOException { + assertSharedRootSlot("sameNamedOldGenericOverrides", "T"); + } + + @Test + public void sameNamedNewGenericOverridesInTwoPackagesShareTheirRootSlot() throws IOException { + assertSharedRootSlot("sameNamedNewGenericOverrides", "T:"); + } + + private void assertSharedRootSlot(String name, String typeParameter) throws IOException { + List units = new ArrayList<>(); + units.add(compilationUnit("Common.wurst", + "package Common", + "public abstract class Box<" + typeParameter + ">", + " abstract function pick(T a, T b) returns T", + "public function pickOf(Box box) returns int", + " return box.pick(10, 20)")); + for (String p : List.of("PA", "PB")) { + boolean first = p.equals("PA"); + units.add(compilationUnit(p + ".wurst", + "package " + p, + "import Common", + "public class First<" + typeParameter + "> extends Box", + " override function pick(T a, T b) returns T", + " return " + (first ? "a" : "b"), + "public function run" + p + "() returns int", + " return pickOf(new First())")); + } + units.add(compilationUnit("Main.wurst", + "package Main", + "import PA", + "import PB", + "native testSuccess()", + "init", + " if runPA() == 10 and runPB() == 20", + " testSuccess()")); + String lua = compileAndRunToLua(name, units.toArray(new CU[0])); + List shuffled = List.of(units.get(3), units.get(2), units.get(0), units.get(1)); + assertEquals(compileToLua(name, shuffled), lua, "the same in any order of the compilation units"); + } + private void assertOwnDispatchGroups(String name, String typeParameter) throws IOException { List units = new ArrayList<>(); for (String p : List.of("PA", "PB")) {