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
Expand Up @@ -423,6 +423,7 @@ public ImProg translateProg() {
translateCompilationUnit(cu);
}
linkBridgedOverrides();
addBridgeFunctions();

if (mainFunc == null) {
mainFunc = ImFunction(emptyTrace, "main", ImTypeVars(), ImVars(), ImVoid(), ImVars(), ImStmts(), flags());
Expand Down Expand Up @@ -483,6 +484,36 @@ private void linkBridgedOverrides() {
}
}

/** The functions {@link #bridgeFunction} made, by class and by the implementation each one runs. */
private final Map<ImClass, Map<FuncDef, ImFunction>> bridgeFunctions = new IdentityHashMap<>();

/**
* The function {@code c} has of its own to run {@code implementation}, an implementation it inherits
* ({@code InterfaceTranslator}), made by {@code make} the first time: a class implementing several interfaces with
* one inherited method gets one function for it.
*/
public ImFunction bridgeFunction(ImClass c, FuncDef implementation, java.util.function.Supplier<ImFunction> make) {
return bridgeFunctions.computeIfAbsent(c, k -> new IdentityHashMap<>())
.computeIfAbsent(implementation, k -> make.get());
}

/**
* Adds the functions of {@link #bridgeFunction} to their classes once every unit is translated. They are made as
* the interfaces are translated, in the order of the compilation units, and two of them can have one name (the
* overloads of a method), which the backends tell apart by their order. So they go in by their sort key, which the
* order of the units does not change.
*/
private void addBridgeFunctions() {
for (ImClass c : imProg.getClasses()) {
Map<FuncDef, ImFunction> functions = bridgeFunctions.get(c);
if (functions != null) {
List<ImFunction> sorted = new ArrayList<>(functions.values());
sortList(sorted);
c.getFunctions().addAll(sorted);
}
}
}

public void removeEmptyPackageInits() {
Set<ImFunction> emptyInitFunctions = new HashSet<>();
for (ImFunction initFunc : new LinkedHashSet<>(initFuncMap.values())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import com.google.common.collect.Lists;
import de.peeeq.wurstscript.ast.ClassDef;
import de.peeeq.wurstscript.ast.ClassOrInterface;
import de.peeeq.wurstscript.ast.FuncDef;
import de.peeeq.wurstscript.ast.InterfaceDef;
import de.peeeq.wurstscript.ast.TypeExpr;
import de.peeeq.wurstscript.ast.TypeParamDef;
import de.peeeq.wurstscript.attributes.CompileError;
import de.peeeq.wurstscript.jassIm.*;
import de.peeeq.wurstscript.translation.imtojass.ImAttrType;
import de.peeeq.wurstscript.types.VariableBinding;
import de.peeeq.wurstscript.types.WurstTypeClass;
import de.peeeq.wurstscript.types.WurstTypeClassOrInterface;
Expand Down Expand Up @@ -103,11 +107,9 @@ private void translateInterfaceFuncDef(FuncDef f) {
ImMethod m = translator.getMethodFor(subM);

ImClass mClass = translator.getClassFor(subC);
if (f.attrHasEmptyBody() && !subClasses.contains(subM.attrNearestClassDef())
&& mClass.getTypeVariables().isEmpty() && m.attrClass().getTypeVariables().isEmpty()) {
if (f.attrHasEmptyBody() && !subClasses.contains(subM.attrNearestClassDef())) {
FuncDef interfaceDefault = defaultOf(subCT, f);
m = methodOfItsOwn(mClass, m, interfaceDefault == null
? m.getImplementation() : translator.getFuncFor(interfaceDefault));
m = methodOfItsOwn(mClass, subCT, m, interfaceDefault == null ? subM : interfaceDefault);
}
OverrideUtils.addOverride(translator, f, mClass, m, subM, typeBinding);
}
Expand All @@ -122,17 +124,101 @@ private void translateInterfaceFuncDef(FuncDef f) {
* own with Base's implementation, which its subclasses inherit. The overrides below C are its sub-methods, as they
* are Base's ({@link ImTranslator#linkOverridesBelow}). Where another interface of C gives m a default, the
* default is the implementation: a default beats an inherited method (a call through that interface runs it on
* every backend), and Lua binds one implementation for C to both. Not for generic classes: a method of a generic
* class is specialised with the functions the class owns, so those keep what they did.
* every backend), and Lua binds one implementation for C to both.
* <p>
* Where neither C nor the class of the implementation is generic, the method runs that implementation's function.
* Otherwise it gets a function of C which calls it ({@link #implementationOfItsOwn}).
*/
private ImMethod methodOfItsOwn(ImClass imClass, ImMethod inherited, ImFunction implementation) {
ImMethod own = JassIm.ImMethod(inherited.getTrace(), translator.selfType(imClass), inherited.getName(),
implementation, Lists.newArrayList(), new java.util.ArrayList<>(), "", false);
private ImMethod methodOfItsOwn(ImClass imClass, WurstTypeClass classType, ImMethod inherited,
FuncDef implementation) {
ImFunction function = translator.getFuncFor(implementation);
ClassOrInterface owner = implementation.attrNearestClassOrInterface();
String name = inherited.getName();
if (!imClass.getTypeVariables().isEmpty()
|| (owner != null && !translator.getClassFor(owner).getTypeVariables().isEmpty())) {
ImFunction inheritedFunction = function;
function = translator.bridgeFunction(imClass, implementation,
() -> implementationOfItsOwn(imClass, classType, implementation, inheritedFunction, owner));
name = function.getName();
}
ImMethod own = JassIm.ImMethod(inherited.getTrace(), translator.selfType(imClass), name,
function, Lists.newArrayList(), new java.util.ArrayList<>(), "", false);
imClass.getMethods().add(own);
translator.linkOverridesBelow(own, inherited);
return own;
}

/**
* A function of {@code imClass} which runs the inherited implementation, for a class where one of the two is
* generic. A method is specialised with the functions of its class, but the inherited function belongs to another
* class and must be specialised for that class's type arguments as {@code imClass} sees them, which need not be
* its own ({@code C<T:> extends Base<string>}). So the function calls the inherited one with this, as
* {@code super.m()} does, and the specialisation finds Base's type arguments from the type of this. It takes the
* inherited function's parameters with Base's type variables replaced by those arguments, and is vararg where that
* function is. The translator adds it to the class ({@link ImTranslator#bridgeFunction}).
*/
private ImFunction implementationOfItsOwn(ImClass imClass, WurstTypeClass classType, FuncDef implementation,
ImFunction inherited, @org.eclipse.jdt.annotation.Nullable ClassOrInterface owner) {
List<ImTypeVar> ownerVariables = owner == null
? Collections.emptyList() : translator.getClassFor(owner).getTypeVariables();
List<ImTypeArgument> ownerArguments = owner == null
? Collections.emptyList() : typeArgumentsAsSeenFrom(classType, owner, ownerVariables);
Comment thread
Frotty marked this conversation as resolved.
ImVar thisVar = JassIm.ImVar(implementation, translator.selfType(imClass), "this", false);
ImVars parameters = JassIm.ImVars(thisVar);
ImExprs arguments = JassIm.ImExprs(JassIm.ImVarAccess(thisVar));
for (ImVar p : inherited.getParameters().subList(1, inherited.getParameters().size())) {
ImVar parameter = JassIm.ImVar(p.getTrace(),
ImAttrType.substituteType(p.getType(), ownerArguments, ownerVariables), p.getName(), false);
parameters.add(parameter);
arguments.add(JassIm.ImVarAccess(parameter));
}
ImType returnType = ImAttrType.substituteType(inherited.getReturnType(), ownerArguments, ownerVariables);
ImExpr call = JassIm.ImFunctionCall(implementation, inherited, JassIm.ImTypeArguments(), arguments, false,
CallType.NORMAL);
ImStmts body = returnType instanceof ImVoid
? JassIm.ImStmts(call)
: JassIm.ImStmts(JassIm.ImReturn(implementation, call));
List<FunctionFlag> flags = new java.util.ArrayList<>();
if (inherited.hasFlag(FunctionFlagEnum.IS_VARARG)) {
flags.add(FunctionFlagEnum.IS_VARARG);
}
return JassIm.ImFunction(implementation, imClass.getName() + "_" + implementation.getName(),
JassIm.ImTypeVars(), parameters, returnType, JassIm.ImVars(), body, flags);
}

/**
* The type arguments for {@code variables}, the type variables of {@code owner}, a class or interface above
* {@code classType}, as that class sees them.
*/
private List<ImTypeArgument> typeArgumentsAsSeenFrom(WurstTypeClass classType, ClassOrInterface owner,
List<ImTypeVar> variables) {
VariableBinding binding = VariableBinding.emptyMapping();
ArrayDeque<WurstTypeClassOrInterface> queue = new ArrayDeque<>();
queue.add(classType);
while (!queue.isEmpty()) {
WurstTypeClassOrInterface type = queue.removeFirst();
if (type.getDef() == owner) {
binding = type.getTypeArgBinding();
break;
}
queue.addAll(type.directSupertypes());
}
List<ImTypeArgument> arguments = new java.util.ArrayList<>();
for (ImTypeVar variable : variables) {
TypeParamDef parameter = translator.getTypeParamDef(variable);
if (parameter == null) {
throw new CompileError(classType.getDef(), "Could not find the type argument of " + owner.getName()
+ " for " + variable.getName() + " as " + classType.getDef().getName() + " sees it.");
}
// A parameter no supertype binds is one of an enclosing generic class, which a static class inside it
// captures: the class sees it as it is, as its own functions do.
ImType type = binding.get(parameter).map(bound -> bound.imTranslateType(translator))
.getOrElse(() -> JassIm.ImTypeVarRef(translator.getTypeVar(parameter)));
Comment thread
Frotty marked this conversation as resolved.
arguments.add(JassIm.ImTypeArgument(type, Collections.emptyMap()));
}
return arguments;
}


/**
* The default which another interface of the class gives {@code abstractMethod}: a method of an interface which
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,63 @@ private static int countOccurrences(String text, String part) {
return n;
}

/**
* A generic class implements two interfaces, each with one of two overloads it inherits, so it gets a function of
* its own for each, both named after the method. The interfaces are in packages of their own: the Jass and the
* Lua must not depend on which of them is translated first. Base comes first either way, so its own functions are
* made in its order: a class's functions are made when they are first asked for, which an interface translated
* before the class does in the order of the units. C comes after both interfaces.
*/
@Test
public void bridgesOfAGenericClassAreTheSameInAnyUnitOrder() throws IOException {
List<CU> units = List.of(
compilationUnit("IntM.wurst",
"package IntM",
"public interface IntM",
" function m(int x) returns int"),
compilationUnit("StrM.wurst",
"package StrM",
"public interface StrM",
" function m(string s) returns int"),
compilationUnit("BaseLib.wurst",
"package BaseLib",
"public class Base",
" function m(int x) returns int",
" return x",
" function m(string s) returns int",
" return 7"),
compilationUnit("Lib.wurst",
"package Lib",
"import BaseLib",
"import IntM",
"import StrM",
"public class C<T:> extends Base implements IntM, StrM"),
compilationUnit("Main.wurst",
"package Main",
"import IntM",
"import StrM",
"import Lib",
"native testSuccess()",
"function viaInt(IntM i) returns int",
" return i.m(5)",
"function viaStr(StrM s) returns int",
" return s.m(\"a\")",
"init",
" if viaInt(new C<int>()) == 5 and viaStr(new C<int>()) == 7",
" testSuccess()"));
String name = "bridgesOfAGenericClass";
File jass = new File("test-output/DeterministicChecks_" + name + "_no_opts.j");
File lua = new File("test-output/lua/DeterministicChecks_" + name + ".lua");
testNamed(name).testLua(true).luaOnly(false).executeProg()
.compilationUnits(units.get(2), units.get(0), units.get(1), units.get(3), units.get(4));
String firstJass = Files.toString(jass, Charsets.UTF_8);
String firstLua = Files.toString(lua, Charsets.UTF_8);
testNamed(name).testLua(true).luaOnly(false).executeProg()
.compilationUnits(units.get(2), units.get(1), units.get(0), units.get(3), units.get(4));
assertEquals(Files.toString(jass, Charsets.UTF_8), firstJass, "Jass must not depend on the unit order");
assertEquals(Files.toString(lua, Charsets.UTF_8), firstLua, "Lua must not depend on the unit order");
}

/** Compiles {@code units} to Lua, runs them, and returns the script, which is written under {@code name}. */
private String compileToLua(String name, List<CU> units) throws IOException {
testNamed(name).testLua(true).executeProg().compilationUnits(units.toArray(new CU[0]));
Expand Down
Loading
Loading