diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java index b4b503730..27771de5d 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java @@ -407,6 +407,12 @@ public JassProg transformProgToJass() { printDebugImProg("./test-output/im " + stage++ + "_classesEliminated.im"); timeTaker.endPhase(); + if (!runArgs.isNoDebugMessages() && runArgs.isIncludeStacktraces()) { + beginPhase(4, "add stack traces"); + new StackTraceInjector2(imProg2, imTranslator2).transform(timeTaker); + timeTaker.endPhase(); + } + new VarargEliminator(imProg2).run(); printDebugImProg("./test-output/im " + stage++ + "_varargEliminated.im"); imTranslator2.assertProperties(); @@ -417,14 +423,8 @@ public JassProg transformProgToJass() { beginPhase(3, "remove debug messages"); DebugMessageRemover.removeDebugMessages(imProg2); timeTaker.endPhase(); - } else { - // debug: add stacktraces - if (runArgs.isIncludeStacktraces()) { - beginPhase(4, "add stack traces"); - new StackTraceInjector2(imProg2, imTranslator2).transform(timeTaker); - timeTaker.endPhase(); - } } + imTranslator2.assertProperties(); ImOptimizer optimizer = new ImOptimizer(timeTaker, imTranslator2); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java index 3c6c1f972..54287da50 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java @@ -2,6 +2,7 @@ import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; +import de.peeeq.wurstscript.attributes.CompileError; import de.peeeq.wurstscript.jassIm.*; import org.jetbrains.annotations.NotNull; @@ -18,6 +19,7 @@ */ public class VarargEliminator { + private static final int JASS_MAX_PARAMETERS = 31; private final ImProg prog; // original + number of args --> new function private final Table varargFuncs = HashBasedTable.create(); @@ -30,7 +32,7 @@ public void run() { // create new vararg functions for (ImFunctionCall c : collectVarargCalls()) { if (c.getFunc().hasFlag(IS_VARARG)) { - generateVarargFunc(c.getFunc(), c.getArguments().size()); + generateVarargFunc(c); } } @@ -66,7 +68,17 @@ public void visit(ImFunctionCall c) { * Generates a function based on the vararg function with the appropriate amount of parameters * for the function call. */ - private void generateVarargFunc(ImFunction func, int numberOfParams) { + private void generateVarargFunc(ImFunctionCall sourceCall) { + ImFunction func = sourceCall.getFunc(); + int numberOfParams = sourceCall.getArguments().size(); + int jassParameterCount = sourceCall.getArguments().stream() + .mapToInt(argument -> flattenedJassArity(argument.attrTyp())) + .sum(); + if (jassParameterCount > JASS_MAX_PARAMETERS) { + throw new CompileError(sourceCall, "Vararg call would generate " + jassParameterCount + + " Jass parameters; the maximum is " + JASS_MAX_PARAMETERS + + ". Use multiple calls (for example with the cascade operator) or pass a collection instead."); + } if (varargFuncs.contains(func, numberOfParams)) { // already generated return; @@ -118,7 +130,7 @@ public void visit(ImVarargLoop imLoop) { params.addAll(list); // generate function for this new call - generateVarargFunc(call.getFunc(), call.getArguments().size()); + generateVarargFunc(call); } @@ -135,6 +147,15 @@ public void visit(ImVarargLoop imLoop) { varargFuncs.put(func, numberOfParams, newFunc); } + private int flattenedJassArity(ImType type) { + if (type instanceof ImTupleType) { + return ((ImTupleType) type).getTypes().stream() + .mapToInt(this::flattenedJassArity) + .sum(); + } + return 1; + } + @NotNull private List collectUsesOfVar(ImFunction newFunc, ImVar varargParam) { List varargParamUses = new ArrayList<>(); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java index d706301eb..3bb01c67f 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/VarargTests.java @@ -4,6 +4,28 @@ public class VarargTests extends WurstScriptTest { + private String arguments(int count) { + StringBuilder result = new StringBuilder(); + for (int i = 1; i <= count; i++) { + if (i > 1) { + result.append(","); + } + result.append(i); + } + return result.toString(); + } + + private String tupleArguments(int count) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < count; i++) { + if (i > 0) { + result.append(","); + } + result.append("pair(1,2)"); + } + return result.toString(); + } + @Test public void testVarargSyntax() { @@ -45,6 +67,134 @@ public void testVarargInput() { ); } + @Test + public void varargAllows31JassParameters() { + testAssertOkLines(false, + "package Test", + "function foo(vararg int ints)", + "init", + " foo(" + arguments(31) + ")" + ); + } + + @Test + public void stacktracedVarargRejectsMoreThan31JassParameters() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "function getStackTraceString() returns string", + " return \"\"", + "function foo(vararg int ints)", + " getStackTraceString()", + "init", + " foo(" + arguments(31) + ")" + ); + } + + @Test + public void stacktracedVarargAllows31JassParameters() { + testAssertOkLines(false, + "package Test", + "function getStackTraceString() returns string", + " return \"\"", + "function foo(vararg int ints)", + " getStackTraceString()", + "init", + " foo(" + arguments(30) + ")" + ); + } + + @Test + public void varargRejectsMoreThan31JassParameters() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "function foo(vararg int ints)", + "init", + " foo(" + arguments(32) + ")" + ); + } + + @Test + public void varargCountsFlattenedTupleParameters() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "tuple pair(int x, int y)", + "function foo(vararg pair pairs)", + "init", + " foo(" + tupleArguments(16) + ")" + ); + } + + @Test + public void varargAllowsFlattenedTupleParametersWithinLimit() { + testAssertOkLines(false, + "package Test", + "tuple pair(int x, int y)", + "function foo(vararg pair pairs)", + "init", + " foo(" + tupleArguments(15) + ")" + ); + } + + @Test + public void varargAllowsMoreThan31ArgumentsInLua() { + test().testLua(true).executeProg().lines( + "package Test", + "native testSuccess()", + "function foo(vararg int ints)", + " var sum = 0", + " for i in ints", + " sum += i", + " if sum == 528", + " testSuccess()", + "init", + " foo(" + arguments(32) + ")" + ); + } + + @Test + public void varargReceiverCountsAsJassParameter() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "class C", + " function foo(vararg int ints)", + "init", + " new C.foo(" + arguments(31) + ")" + ); + } + + @Test + public void varargReceiverAllows31JassParametersWithStacktraces() { + testAssertOkLines(false, + "package Test", + "class C", + " function foo(vararg int ints)", + "init", + " new C.foo(" + arguments(29) + ")" + ); + } + + @Test + public void varargConstructedObjectCountsAsJassParameter() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "class C", + " construct(vararg int ints)", + "init", + " new C(" + arguments(31) + ")" + ); + } + + @Test + public void varargDelegatingConstructorCountsConstructedObject() { + testAssertErrorsLines(false, "would generate 32 Jass parameters; the maximum is 31", + "package Test", + "class C", + " construct()", + " this(" + arguments(31) + ")", + " construct(vararg int ints)" + ); + } + @Test public void testVarargForeach() { testAssertOkLines(true,