Skip to content

Commit 2668533

Browse files
authored
Allow nested generic specialization keys (#1266)
* Allow nested generic specialization keys * Normalize nested specialization bindings * Isolate Lua generic static storage * Track erased generic static ownership * Materialize statics for erased instantiations * Preserve generic static initializer order * Specialize static-owning generic factories * Handle inherited generic static storage * Register erased generic helper statics * Collect fixed generic method operations * Preserve implicit generic reachability
1 parent 5687330 commit 2668533

7 files changed

Lines changed: 871 additions & 43 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -875,10 +875,11 @@ public LuaCompilationUnit transformProgToLua() {
875875
ImAttrType.setWurstClassType(null);
876876
int stage;
877877
boolean specializeTupleValueTypes = containsTupleTypeArgument();
878-
if (containsGenericNewCall() || containsTypeClassDispatch() || specializeTupleValueTypes) {
878+
EliminateGenerics luaGenerics = new EliminateGenerics(getImTranslator(), getImProg());
879+
if (containsGenericNewCall() || containsTypeClassDispatch() || specializeTupleValueTypes
880+
|| luaGenerics.hasGenericStatics()) {
879881
beginPhase(2, "Specialize generics for Lua-only concrete operations");
880-
new EliminateGenerics(getImTranslator(), getImProg())
881-
.transformGenericNewOnly(specializeTupleValueTypes);
882+
luaGenerics.transformGenericNewOnly(specializeTupleValueTypes);
882883
// Remove phantom erased initialization before optimization can preserve only its side
883884
// effect. A specialized static owns its copied initializer unless the erased static is live.
884885
RemoveGarbage.removePhantomGenericStaticInitializers(getImProg(), getImTranslator());

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java

Lines changed: 209 additions & 31 deletions
Large diffs are not rendered by default.

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/GenericTypes.java

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package de.peeeq.wurstscript.translation.imtranslation;
22

3-
import com.google.common.base.Preconditions;
43
import com.google.common.collect.ImmutableList;
54
import de.peeeq.wurstscript.jassIm.*;
65

@@ -16,9 +15,6 @@ class GenericTypes {
1615

1716

1817
public GenericTypes(List<ImTypeArgument> typeArguments) {
19-
for (ImTypeArgument ta : typeArguments) {
20-
Preconditions.checkArgument(!EliminateGenerics.isGenericType(ta.getType()), "Type arguments must not be generic: " + typeArguments);
21-
}
2218
this.typeArguments = ImmutableList.copyOf(typeArguments);
2319
}
2420

@@ -36,7 +32,7 @@ public boolean equals(Object o) {
3632
for (int i = 0; i < typeArguments.size(); i++) {
3733
ImTypeArgument t1 = typeArguments.get(i);
3834
ImTypeArgument t2 = ot.typeArguments.get(i);
39-
if (!t1.getType().equalsType(t2.getType())) {
35+
if (!equalTypeIgnoringBindings(t1.getType(), t2.getType())) {
4036
return false;
4137
}
4238
// Deliberately not comparing the type class binding. It is only a fast path for
@@ -50,6 +46,60 @@ public boolean equals(Object o) {
5046
return false;
5147
}
5248

49+
/**
50+
* Type-class bindings are dispatch metadata, not part of a specialization's structural type.
51+
* Unlike the general IM type equality operation, this comparison therefore ignores bindings
52+
* on every nested class-type argument, not just on the arguments wrapped by this key.
53+
*/
54+
private static boolean equalTypeIgnoringBindings(ImType left, ImType right) {
55+
if (left instanceof ImArrayType) {
56+
return right instanceof ImArrayType
57+
&& equalTypeIgnoringBindings(((ImArrayType) left).getEntryType(),
58+
((ImArrayType) right).getEntryType());
59+
}
60+
if (left instanceof ImArrayTypeMulti) {
61+
return right instanceof ImArrayTypeMulti
62+
&& equalTypeIgnoringBindings(((ImArrayTypeMulti) left).getEntryType(),
63+
((ImArrayTypeMulti) right).getEntryType());
64+
}
65+
if (left instanceof ImTupleType) {
66+
if (!(right instanceof ImTupleType)) {
67+
return false;
68+
}
69+
ImTupleType leftTuple = (ImTupleType) left;
70+
ImTupleType rightTuple = (ImTupleType) right;
71+
if (leftTuple.getTypes().size() != rightTuple.getTypes().size()) {
72+
return false;
73+
}
74+
for (int i = 0; i < leftTuple.getTypes().size(); i++) {
75+
if (!equalTypeIgnoringBindings(leftTuple.getTypes().get(i),
76+
rightTuple.getTypes().get(i))) {
77+
return false;
78+
}
79+
}
80+
return true;
81+
}
82+
if (left instanceof ImClassType) {
83+
if (!(right instanceof ImClassType)) {
84+
return false;
85+
}
86+
ImClassType leftClass = (ImClassType) left;
87+
ImClassType rightClass = (ImClassType) right;
88+
if (leftClass.getClassDef() != rightClass.getClassDef()
89+
|| leftClass.getTypeArguments().size() != rightClass.getTypeArguments().size()) {
90+
return false;
91+
}
92+
for (int i = 0; i < leftClass.getTypeArguments().size(); i++) {
93+
if (!equalTypeIgnoringBindings(leftClass.getTypeArguments().get(i).getType(),
94+
rightClass.getTypeArguments().get(i).getType())) {
95+
return false;
96+
}
97+
}
98+
return true;
99+
}
100+
return left.equalsType(right);
101+
}
102+
53103
@Override
54104
public int hashCode() {
55105
int res = 7;

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public record Specialisation(Element original, List<ImTypeArgument> typeArgument
6161
}
6262

6363
private final Map<Element, Specialisation> specialisations = new IdentityHashMap<>();
64+
private final Map<ImClass, Set<GenericTypes>> erasedGenericAllocations = new IdentityHashMap<>();
6465

6566
/**
6667
* @param typeArguments the arguments the copy was made for, empty when a copy carries none of its
@@ -99,6 +100,31 @@ public void recordGenericStaticOwner(ImVar global, ImClass owner) {
99100
return specialisations.get(copy);
100101
}
101102

103+
public void recordErasedGenericAllocation(ImClass clazz, List<ImTypeArgument> typeArguments) {
104+
erasedGenericAllocations.computeIfAbsent(canonical(clazz), ignored -> new HashSet<>())
105+
.add(new GenericTypes(typeArguments));
106+
}
107+
108+
public boolean hasErasedAllocationWithoutStaticSpecialization(ImClass clazz, ImVar originalStatic) {
109+
Set<GenericTypes> allocations = erasedGenericAllocations.get(canonical(clazz));
110+
if (allocations == null || allocations.isEmpty()) {
111+
return false;
112+
}
113+
Set<GenericTypes> specializedStatics = new HashSet<>();
114+
for (Map.Entry<Element, Specialisation> entry : specialisations.entrySet()) {
115+
Specialisation specialization = entry.getValue();
116+
if (specialization.original() == originalStatic) {
117+
specializedStatics.add(new GenericTypes(specialization.typeArguments()));
118+
}
119+
}
120+
for (GenericTypes allocation : allocations) {
121+
if (!specializedStatics.contains(allocation)) {
122+
return true;
123+
}
124+
}
125+
return false;
126+
}
127+
102128
/**
103129
* The node {@code copy} was ultimately copied from, or {@code copy} itself.
104130
* <p>

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/RemoveGarbage.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ public static void removePhantomGenericStaticInitializers(ImProg prog, ImTransla
190190
changed = false;
191191
for (ImVar original : candidates.keySet()) {
192192
ImClass owner = translator.genericStaticOwnerOf(original);
193-
if ((used.getVars().contains(original) || used.getInstantiatedClasses().contains(owner))
193+
boolean erasedInstantiationNeedsOriginal = used.getInstantiatedClasses().contains(owner)
194+
&& translator.hasErasedAllocationWithoutStaticSpecialization(owner, original);
195+
if ((used.getVars().contains(original) || erasedInstantiationNeedsOriginal)
194196
&& liveOriginals.add(original)) {
195197
changed = true;
196198
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package de.peeeq.wurstscript.translation.imtranslation;
2+
3+
import de.peeeq.wurstscript.ast.Ast;
4+
import de.peeeq.wurstscript.jassIm.ImClass;
5+
import de.peeeq.wurstscript.jassIm.ImClassType;
6+
import de.peeeq.wurstscript.jassIm.ImFunction;
7+
import de.peeeq.wurstscript.jassIm.ImMethod;
8+
import de.peeeq.wurstscript.jassIm.ImSimpleType;
9+
import de.peeeq.wurstscript.jassIm.ImTypeArgument;
10+
import de.peeeq.wurstscript.jassIm.ImTypeClassFunc;
11+
import de.peeeq.wurstscript.jassIm.JassIm;
12+
import io.vavr.control.Either;
13+
import org.testng.annotations.Test;
14+
15+
import java.util.Collections;
16+
import java.util.LinkedHashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
20+
import static org.testng.Assert.assertEquals;
21+
22+
public class GenericTypesTests {
23+
24+
@Test
25+
public void nestedTypeClassBindingsDoNotSplitSpecializationKeys() {
26+
ImClass box = genericClass("Box");
27+
ImClass list = genericClass("List");
28+
ImSimpleType integer = JassIm.ImSimpleType("integer");
29+
ImTypeClassFunc requirement = JassIm.ImTypeClassFunc(Ast.NoExpr(), "toIndex",
30+
JassIm.ImTypeVars(), JassIm.ImVars(), integer);
31+
ImFunction instance = JassIm.ImFunction(Ast.NoExpr(), "intToIndex", JassIm.ImTypeVars(),
32+
JassIm.ImVars(), integer, JassIm.ImVars(), JassIm.ImStmts(), List.of());
33+
34+
Map<ImTypeClassFunc, Either<ImMethod, ImFunction>> binding = new LinkedHashMap<>();
35+
binding.put(requirement, Either.right(instance));
36+
ImClassType unboundBox = JassIm.ImClassType(box,
37+
JassIm.ImTypeArguments(argument(integer, Collections.emptyMap())));
38+
ImClassType boundBox = JassIm.ImClassType(box,
39+
JassIm.ImTypeArguments(argument(integer, binding)));
40+
41+
GenericTypes unbound = key(list, unboundBox);
42+
GenericTypes bound = key(list, boundBox);
43+
44+
assertEquals(bound, unbound,
45+
"type-class dispatch metadata must not change a structural specialization key");
46+
assertEquals(bound.hashCode(), unbound.hashCode());
47+
}
48+
49+
private static GenericTypes key(ImClass list, ImClassType nestedType) {
50+
ImClassType listType = JassIm.ImClassType(list,
51+
JassIm.ImTypeArguments(argument(nestedType, Collections.emptyMap())));
52+
return new GenericTypes(List.of(argument(listType, Collections.emptyMap())));
53+
}
54+
55+
private static ImTypeArgument argument(de.peeeq.wurstscript.jassIm.ImType type,
56+
Map<ImTypeClassFunc, Either<ImMethod, ImFunction>> binding) {
57+
return JassIm.ImTypeArgument(type, binding);
58+
}
59+
60+
private static ImClass genericClass(String name) {
61+
return JassIm.ImClass(Ast.NoExpr(), name, JassIm.ImTypeVars(JassIm.ImTypeVar("T")),
62+
JassIm.ImVars(), JassIm.ImMethods(), JassIm.ImFunctions(), List.of());
63+
}
64+
}

0 commit comments

Comments
 (0)