Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1192,10 +1192,14 @@ private String typeKey(ImType type) {
private Set<String> collectDispatchSlotNames(ImClass receiverClass, List<ImMethod> groupMethods) {
Set<String> slotNames = new TreeSet<>();
Set<String> semanticNames = new TreeSet<>();
Set<String> dispatchKeys = new TreeSet<>();
Set<String> closureRuntimeKeys = new TreeSet<>();
for (ImMethod m : groupMethods) {
if (m == null) {
continue;
}
dispatchKeys.add(dispatchSignatureKey(m));
closureRuntimeKeys.add(closureRuntimeDispatchKey(m));
String methodName = m.getName();
if (!methodName.isEmpty()) {
slotNames.add(methodName);
Expand All @@ -1215,6 +1219,12 @@ private Set<String> collectDispatchSlotNames(ImClass receiverClass, List<ImMetho
slotNames.add(owner.getName() + "_" + sourceSemanticName);
}
}
if (receiverClass != null && !dispatchKeys.isEmpty() && !semanticNames.isEmpty()) {
collectEquivalentHierarchyMethodNames(receiverClass, dispatchKeys, semanticNames, slotNames, new HashSet<>());
if (isClosureGeneratedClass(receiverClass) && !closureRuntimeKeys.isEmpty()) {
collectEquivalentClosureFamilyMethodNames(receiverClass, closureRuntimeKeys, semanticNames, slotNames);
}
}
if (receiverClass != null && !semanticNames.isEmpty()) {
Set<String> classNames = new TreeSet<>();
collectClassNamesInHierarchy(receiverClass, classNames, new HashSet<>());
Expand All @@ -1227,6 +1237,105 @@ private Set<String> collectDispatchSlotNames(ImClass receiverClass, List<ImMetho
return slotNames;
}

private void collectEquivalentHierarchyMethodNames(ImClass c, Set<String> dispatchKeys, Set<String> semanticNames,
Set<String> slotNames, Set<ImClass> visited) {
if (c == null || !visited.add(c)) {
return;
}
for (ImMethod method : c.getMethods()) {
if (method == null) {
continue;
}
if (!dispatchKeys.contains(dispatchSignatureKey(method))) {
continue;
}
String methodName = method.getName();
String semanticName = semanticNameFromMethodName(methodName);
String sourceSemanticName = sourceSemanticName(method);
if (!semanticNames.contains(semanticName) && !semanticNames.contains(sourceSemanticName)) {
continue;
}
if (!methodName.isEmpty()) {
slotNames.add(methodName);
slotNames.add(c.getName() + "_" + methodName);
}
}
for (ImClassType sc : c.getSuperClasses()) {
collectEquivalentHierarchyMethodNames(sc.getClassDef(), dispatchKeys, semanticNames, slotNames, visited);
}
}

private void collectEquivalentClosureFamilyMethodNames(ImClass receiverClass, Set<String> closureRuntimeKeys,
Set<String> semanticNames, Set<String> slotNames) {
Set<ImClass> anchors = new HashSet<>();
collectClosureFamilyAnchors(receiverClass, anchors, new HashSet<>());
if (anchors.isEmpty()) {
return;
}
List<ImClass> classes = new ArrayList<>(prog.getClasses());
classes.sort(Comparator.comparing(this::classSortKey));
for (ImClass candidateClass : classes) {
if (!sharesClosureFamilyAnchor(candidateClass, anchors, new HashSet<>())) {
continue;
Comment thread
Frotty marked this conversation as resolved.
Outdated
}
List<ImMethod> methods = new ArrayList<>(candidateClass.getMethods());
methods.sort(Comparator.comparing(this::methodSortKey));
for (ImMethod method : methods) {
if (!closureRuntimeKeys.contains(closureRuntimeDispatchKey(method))) {
continue;
}
String methodName = method.getName();
String semanticName = semanticNameFromMethodName(methodName);
String sourceSemanticName = sourceSemanticName(method);
if (!semanticNames.contains(semanticName) && !semanticNames.contains(sourceSemanticName)) {
continue;
}
if (!methodName.isEmpty()) {
slotNames.add(methodName);
slotNames.add(candidateClass.getName() + "_" + methodName);
}
}
}
}

private String closureRuntimeDispatchKey(ImMethod method) {
if (method == null) {
return "<null>";
}
ImFunction implementation = resolveDispatchSignatureImplementation(method, new HashSet<>());
if (implementation == null) {
return "<abstract>";
}
return "" + Math.max(0, implementation.getParameters().size() - 1);
}

private void collectClosureFamilyAnchors(ImClass c, Set<ImClass> anchors, Set<ImClass> visited) {
if (c == null || !visited.add(c)) {
return;
}
if (!isClosureGeneratedClass(c)) {
anchors.add(c);
}
for (ImClassType sc : c.getSuperClasses()) {
collectClosureFamilyAnchors(sc.getClassDef(), anchors, visited);
}
}

private boolean sharesClosureFamilyAnchor(ImClass c, Set<ImClass> anchors, Set<ImClass> visited) {
if (c == null || !visited.add(c)) {
return false;
}
if (anchors.contains(c)) {
return true;
}
for (ImClassType sc : c.getSuperClasses()) {
if (sharesClosureFamilyAnchor(sc.getClassDef(), anchors, visited)) {
return true;
}
}
return false;
}

private void collectClassNamesInHierarchy(ImClass c, Set<String> out, Set<ImClass> visited) {
if (c == null || !visited.add(c)) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,18 @@ private List<String> nonBaseSubclassBindings(String output, String baseName, Str
return result;
}

private List<String> subclassCreateClasses(String output, String baseName) {
Matcher matcher = Pattern.compile("function\\s+(" + Pattern.quote(baseName) + "_[A-Za-z0-9_]+):create\\d*\\s*\\(").matcher(output);
List<String> result = new ArrayList<>();
while (matcher.find()) {
String owner = matcher.group(1);
if (!result.contains(owner)) {
result.add(owner);
}
}
return result;
}

private String compileLuaWithRunArgs(String testName, boolean withStdLib, String... lines) {
RunArgs runArgs = new RunArgs().with("-lua", "-inline", "-localOptimizations", "-stacktraces");
WurstGui gui = new WurstGuiCliImpl();
Expand Down Expand Up @@ -903,6 +915,74 @@ public void nestedGenericLinkedListClosuresKeepPrefixedLuaSlotNames() throws IOE
assertContainsRegex(compiled, "LLItrClosure_[A-Za-z0-9_]+\\.LLItrClosure_run\\s*=");
}

@Test
public void erasedLinkedListClosureCallsitesKeepSuffixedRunSlotsAlignedAcrossAllSubclasses() {
String compiled = compileLuaWithRunArgs(
"LuaTranslationTests_erasedLinkedListClosureCallsitesKeepSuffixedRunSlotsAlignedAcrossAllSubclasses",
false,
"package Test",
"interface Alpha0",
" function run()",
"interface Alpha1",
" function run()",
"interface Alpha2",
" function run()",
"interface Alpha3",
" function run()",
"interface Alpha4",
" function run()",
"interface Alpha5",
" function run()",
"public interface MapClosure<T,S>",
" function run(T t) returns S",
"public interface LLItrClosure<T>",
" function run(T t)",
"class Node<T>",
" T elem",
" Node<T> next = null",
"class LinkedList<T>",
" Node<T> first = null",
" function add(T elem)",
" let n = new Node<T>()",
" n.elem = elem",
" n.next = first",
" first = n",
" function forEach(LLItrClosure<T> itr)",
" var cur = first",
" while cur != null",
" itr.run(cur.elem)",
" cur = cur.next",
" function map<S>(MapClosure<T,S> itr) returns LinkedList<S>",
" let out = new LinkedList<S>()",
" forEach() t ->",
" out.add(itr.run(t))",
" return out",
"function usePlain(LinkedList<int> xs)",
" xs.forEach() x ->",
" skip",
"function useMapped(LinkedList<int> xs)",
" xs.map((int x) -> x + 1)",
"function useNested(LinkedList<int> xs)",
" xs.forEach() x ->",
" xs.map((int y) -> y + x)",
"init",
" let xs = new LinkedList<int>()",
" xs.add(1)",
" usePlain(xs)",
" useMapped(xs)",
" useNested(xs)"
);

List<String> subclasses = subclassCreateClasses(compiled, "LLItrClosure");
assertTrue("Expected multiple generated LLItrClosure subclasses", subclasses.size() >= 3);
List<String> suffixedSlots = uniqueMatches(compiled, "LLItrClosure_[A-Za-z0-9_]+\\.(run\\d+)\\s*=", 1);
assertTrue("Expected at least one suffixed LLItrClosure run slot", !suffixedSlots.isEmpty());
String slotName = suffixedSlots.get(0);
for (String subclass : subclasses) {
assertContainsRegex(compiled, Pattern.quote(subclass) + "\\." + Pattern.quote(slotName) + "\\s*=");
}
}

@Test
public void mainAndConfigNamesFixed() throws IOException {
test().testLua(true).lines(
Expand Down
Loading