Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
@@ -1,6 +1,5 @@
package de.peeeq.wurstscript.translation.imtranslation;

import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.Lists;
import de.peeeq.wurstscript.ast.ClassDef;
import de.peeeq.wurstscript.ast.FuncDef;
Expand All @@ -9,9 +8,11 @@
import de.peeeq.wurstscript.jassIm.*;
import de.peeeq.wurstscript.types.VariableBinding;
import de.peeeq.wurstscript.types.WurstTypeClass;
import de.peeeq.wurstscript.types.WurstTypeClassOrInterface;
import de.peeeq.wurstscript.types.WurstTypeInterface;
import de.peeeq.wurstscript.types.WurstTypeNamedScope;

import java.util.ArrayDeque;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -89,24 +90,14 @@ private void translateInterfaceFuncDef(FuncDef f) {


List<ClassDef> subClasses = Lists.newArrayList(translator.getInterfaceInstances(interfaceDef));
// TODO also add extended interfaces

// set sub methods
Map<ClassDef, FuncDef> subClasses2 = translator.getClassesWithImplementation(subClasses, f);
for (Entry<ClassDef, FuncDef> subE : subClasses2.entrySet()) {
ClassDef subC = subE.getKey();
WurstTypeClass subCT = subC.attrTypC();
ImmutableCollection<WurstTypeInterface> interfaces = subCT.implementedInterfaces();

VariableBinding typeBinding =
VariableBinding.emptyMapping();
for (WurstTypeInterface t : interfaces) {
if (t.getDef() == interfaceDef) {
VariableBinding typeArgBinding = t.getTypeArgBinding();
typeBinding = typeArgBinding;
break;
}
}

VariableBinding typeBinding = typeBindingOf(subCT);

FuncDef subM = subE.getValue();
ImMethod m = translator.getMethodFor(subM);
Expand All @@ -117,4 +108,32 @@ private void translateInterfaceFuncDef(FuncDef f) {

}

/**
* The type arguments a class gives this interface where it implements it: itself, through an interface which
* extends this one, or through a superclass (the instances are the subclasses of an implementing class too). The
* nearest declaration binds, and at the same distance the class's own interfaces before its superclass, so a
* class which implements {@code I<real>} itself is not bound by the {@code I<int>} of its superclass. The override
* converts its arguments from their index with them.
*/
private VariableBinding typeBindingOf(WurstTypeClass classType) {
ArrayDeque<WurstTypeClassOrInterface> queue = new ArrayDeque<>();
queue.add(classType);
while (!queue.isEmpty()) {
WurstTypeClassOrInterface type = queue.removeFirst();
if (type instanceof WurstTypeInterface i && i.getDef() == interfaceDef) {
return i.getTypeArgBinding();
}
if (type instanceof WurstTypeClass c) {
queue.addAll(c.implementedInterfaces());
WurstTypeClass extended = c.extendedClass();
if (extended != null) {
queue.add(extended);
}
} else {
queue.addAll(type.directSupertypes());
}
}
return VariableBinding.emptyMapping();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,82 @@ public void implicitConversions5() { // #490
);
}

/** {@code I<real>} with the conversions of real, implemented by B and through B by its subclasses. */
private static final String[] BRIDGED_INTERFACE = {
"package test",
"native testSuccess()",
"@extern native R2I(real r) returns int",
"function realToIndex(real r) returns int",
" return R2I(r * 10.)",
"function realFromIndex(int i) returns real",
" return i / 10.",
"interface I<T>",
" function foo(T t) returns int",
"interface J<T> extends I<T>",
"class B implements I<real>",
" override function foo(real t) returns int",
" return R2I(t * 100.)",
"function viaI(I<real> i) returns int",
" return i.foo(1.5)"};

private static String[] bridged(String... rest) {
String[] result = java.util.Arrays.copyOf(BRIDGED_INTERFACE, BRIDGED_INTERFACE.length + rest.length);
System.arraycopy(rest, 0, result, BRIDGED_INTERFACE.length, rest.length);
return result;
}

/**
* The override in a subclass of the class which implements the interface is called through the interface with
* the argument converted from its index, as the implementation of the superclass is.
*/
@Test
public void aSubclassOverrideIsCalledThroughTheInterfaceWithTheConvertedArgument() throws java.io.IOException {
test().testLua(true).luaOnly(false).executeProg().lines(bridged(
Comment thread
Frotty marked this conversation as resolved.
"class D extends B",
" override function foo(real t) returns int",
" return R2I(t * 1000.)",
"init",
" if viaI(new B()) == 150 and viaI(new D()) == 1500",
" testSuccess()"));

String name = "GenericsTests_aSubclassOverrideIsCalledThroughTheInterfaceWithTheConvertedArgument";
// Jass: D has its own bridge, which converts the index back, and the dispatch of I.foo calls it
String jass = java.nio.file.Files.readString(java.nio.file.Path.of("test-output", name + "_no_opts.j"));
org.testng.Assert.assertTrue(jass.contains("function D_foo_wrapper takes integer this, integer t returns integer"), jass);
org.testng.Assert.assertTrue(jass.contains("return D_foo(this, realFromIndex(t))"), jass);
org.testng.Assert.assertTrue(jass.contains("= D_foo_wrapper(this, t)"), "the dispatch calls D's bridge:\n" + jass);
// Lua: D binds the slot the call reads to its own bridge, not B's
String lua = java.nio.file.Files.readString(java.nio.file.Path.of("test-output", "lua", name + ".lua"));
java.util.regex.Matcher call = java.util.regex.Pattern.compile("__wurst_objectClass\\[\\w+\\]\\.(\\w+)\\(").matcher(lua);
org.testng.Assert.assertTrue(call.find(), lua);
String slot = call.group(1);
org.testng.Assert.assertTrue(lua.contains("D." + slot + " = D_foo_wrapper"), "D binds " + slot + " to its bridge:\n" + lua);
org.testng.Assert.assertTrue(lua.contains("B." + slot + " = B_foo_wrapper"), "B keeps its own:\n" + lua);
java.util.regex.Matcher bridge = java.util.regex.Pattern.compile("function D_foo_wrapper\\((\\w+), (\\w+)\\) \\s*return (\\w+)\\(\\1, realFromIndex\\(\\2\\)\\)").matcher(lua);
org.testng.Assert.assertTrue(bridge.find(), "D's bridge converts the argument back:\n" + lua);
}

@Test
public void aSubclassWithoutAnOverrideIsCalledThroughTheInterfaceWithTheConvertedArgument() {
test().testLua(true).luaOnly(false).executeProg().lines(bridged(
"class D extends B",
"init",
" if viaI(new D()) == 150",
" testSuccess()"));
}

/** The interface is implemented through one which extends it, with the type arguments of that one. */
@Test
public void anImplementationOfAnExtendingInterfaceIsCalledWithTheConvertedArgument() {
test().testLua(true).luaOnly(false).executeProg().lines(bridged(
"class K implements J<real>",
" override function foo(real t) returns int",
" return R2I(t * 1000.)",
"init",
" if viaI(new K()) == 1500",
" testSuccess()"));
}

@Test
public void implicitConversionsFail() {
testAssertErrorsLines(true, "Could not find function blaFromIndex",
Expand Down
Loading