From fb0ec573e02f6a75635a4e31d6c80deff11c8065 Mon Sep 17 00:00:00 2001 From: Sergey Mashkov Date: Tue, 26 Nov 2013 17:33:06 +0400 Subject: [PATCH] Tests for JVM back-end support of tail-call optimization --- .../functions/tail-recursion/defaultArgs.kt | 13 ++ .../functions/tail-recursion/insideElvis.kt | 10 + .../tail-recursion/labeledThisReferences.kt | 25 +++ .../box/functions/tail-recursion/loops.kt | 14 ++ .../tail-recursion/multilevelBlocks.kt | 15 ++ .../tail-recursion/realIteratorFoldl.kt | 11 ++ .../tail-recursion/realStringEscape.kt | 16 ++ .../tail-recursion/realStringRepeat.kt | 8 + .../tail-recursion/recursiveInnerFunction.kt | 11 ++ .../functions/tail-recursion/returnInCatch.kt | 11 ++ .../tail-recursion/returnInFinally.kt | 11 ++ .../tail-recursion/returnInIfInFinally.kt | 15 ++ .../functions/tail-recursion/returnInTry.kt | 11 ++ .../functions/tail-recursion/simpleBlock.kt | 11 ++ .../functions/tail-recursion/simpleReturn.kt | 11 ++ .../tail-recursion/simpleReturnWithElse.kt | 12 ++ .../tail-recursion/thisReferences.kt | 23 +++ .../functions/tail-recursion/unitBlocks.kt | 21 ++ .../tail-recursion/whenWithCondition.kt | 8 + .../tail-recursion/whenWithInRange.kt | 11 ++ .../functions/tail-recursion/whenWithIs.kt | 15 ++ .../tail-recursion/whenWithoutCondition.kt | 9 + .../checkers/AbstractTailRecursionTest.java | 180 ++++++++++++++++++ .../TailRecursionDetectorTestGenerated.java | 149 +++++++++++++++ .../BlackBoxCodegenTestGenerated.java | 121 +++++++++++- .../jet/generators/tests/GenerateTests.java | 8 + 26 files changed, 749 insertions(+), 1 deletion(-) create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/defaultArgs.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/insideElvis.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/labeledThisReferences.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/loops.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/multilevelBlocks.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/realIteratorFoldl.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/realStringEscape.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/realStringRepeat.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/recursiveInnerFunction.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/returnInCatch.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/returnInFinally.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/returnInIfInFinally.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/returnInTry.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/simpleBlock.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/simpleReturn.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/simpleReturnWithElse.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/thisReferences.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/unitBlocks.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/whenWithCondition.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/whenWithInRange.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/whenWithIs.kt create mode 100644 compiler/testData/codegen/box/functions/tail-recursion/whenWithoutCondition.kt create mode 100644 compiler/tests/org/jetbrains/jet/checkers/AbstractTailRecursionTest.java create mode 100644 compiler/tests/org/jetbrains/jet/checkers/TailRecursionDetectorTestGenerated.java diff --git a/compiler/testData/codegen/box/functions/tail-recursion/defaultArgs.kt b/compiler/testData/codegen/box/functions/tail-recursion/defaultArgs.kt new file mode 100644 index 00000000000..c611fd0b6da --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/defaultArgs.kt @@ -0,0 +1,13 @@ +tailRecursive fun test(x : Int = 0, e : Any = "a") { + if (!e.equals("a")) { + throw IllegalArgumentException() + } + if (x > 0) { + test(x - 1) + } +} + +fun box() : String { + test(100000) + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/insideElvis.kt b/compiler/testData/codegen/box/functions/tail-recursion/insideElvis.kt new file mode 100644 index 00000000000..9c94e4bca87 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/insideElvis.kt @@ -0,0 +1,10 @@ +tailRecursive fun test(counter : Int, a : Any) : Int? { + if (counter < 0) return null + if (counter == 0) return 777 + + return test(-1, "no tail") ?: test(-2, "no tail") ?: test(counter - 1, "tail") +} + +fun box() : String = + if (test(100000, "test") == 777) "OK" + else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/labeledThisReferences.kt b/compiler/testData/codegen/box/functions/tail-recursion/labeledThisReferences.kt new file mode 100644 index 00000000000..2558c0415bf --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/labeledThisReferences.kt @@ -0,0 +1,25 @@ +class B { + inner class C { + tailRecursive fun h(counter : Int, x : Any) { + if (counter > 0) { + this@C.h(counter - 1, "tail") + } + } + + tailRecursive fun h2(x : Any) { + this@B.h2("no recursion") // keep vigilance + } + + } + + fun makeC() : C = C() + + fun h2(x : Any) { + } +} + +fun box() : String { + B().makeC().h(1000000, "test") + B().makeC().h2(0) + return "OK" +} diff --git a/compiler/testData/codegen/box/functions/tail-recursion/loops.kt b/compiler/testData/codegen/box/functions/tail-recursion/loops.kt new file mode 100644 index 00000000000..3ab9bb226ea --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/loops.kt @@ -0,0 +1,14 @@ +tailRecursive fun test(x : Int, a : Any) : Int { + var z = if (x > 3) 3 else x + while (z > 0) { + if (z > 10) { + return test(x - 1, "tail") + } + test(0, "no tail") + z = z - 1 + } + + return 1 +} + +fun box() : String = if (test(100000, "test") == 1) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/multilevelBlocks.kt b/compiler/testData/codegen/box/functions/tail-recursion/multilevelBlocks.kt new file mode 100644 index 00000000000..b6cc5acc69d --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/multilevelBlocks.kt @@ -0,0 +1,15 @@ +tailRecursive fun test(x : Int, a : Any) : Int { + if (x == 1) { + if (x != 1) { + test(0, "no tail") + return test(0, "tail") + } else { + return test(x + test(0, "no tail"), "tail") + } + } else if (x > 0) { + return test(x - 1, "tail") + } + return -1 +} + +fun box() : String = if (test(1000000, "test") == -1) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/realIteratorFoldl.kt b/compiler/testData/codegen/box/functions/tail-recursion/realIteratorFoldl.kt new file mode 100644 index 00000000000..74961bc58d9 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/realIteratorFoldl.kt @@ -0,0 +1,11 @@ +tailRecursive fun Iterator.foldl(acc : A, foldFunction : (e : T, acc : A) -> A) : A = + if (!hasNext()) acc + else foldl(foldFunction(next(), acc), foldFunction) + +fun box() : String { + val sum = (1..1000000).iterator().foldl(0) { (e : Int, acc : Long) -> + acc + e + } + + return if (sum == 500000500000) "OK" else "FAIL: $sum" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/realStringEscape.kt b/compiler/testData/codegen/box/functions/tail-recursion/realStringEscape.kt new file mode 100644 index 00000000000..28ad0abc393 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/realStringEscape.kt @@ -0,0 +1,16 @@ +fun escapeChar(c : Char) : String? = when (c) { + '\\' -> "\\\\" + '\n' -> "\\n" + '"' -> "\\\"" + else -> "" + c +} + +tailRecursive fun String.escape(i : Int = 0, result : StringBuilder = StringBuilder()) : String = + if (i == length) result.toString() + else escape(i + 1, result.append(escapeChar(get(i)))) + +fun box() : String { + "test me not \\".escape() + return "OK" +} + diff --git a/compiler/testData/codegen/box/functions/tail-recursion/realStringRepeat.kt b/compiler/testData/codegen/box/functions/tail-recursion/realStringRepeat.kt new file mode 100644 index 00000000000..98b6863ca21 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/realStringRepeat.kt @@ -0,0 +1,8 @@ +tailRecursive fun String.repeat(num : Int, acc : StringBuilder = StringBuilder()) : String = + if (num == 0) acc.toString() + else repeat(num - 1, acc.append(this)!!) + +fun box() : String { + val s = "a".repeat(10000) + return if (s.length == 10000) "OK" else "FAIL: ${s.length}" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/recursiveInnerFunction.kt b/compiler/testData/codegen/box/functions/tail-recursion/recursiveInnerFunction.kt new file mode 100644 index 00000000000..56e67df8afe --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/recursiveInnerFunction.kt @@ -0,0 +1,11 @@ +fun test() { + [tailRecursive] fun g3(counter : Int, x : Any) { + if (counter > 0) { g3(counter - 1, "tail") } + } + g3(1000000, "test") +} + +fun box() : String { + test() + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/returnInCatch.kt b/compiler/testData/codegen/box/functions/tail-recursion/returnInCatch.kt new file mode 100644 index 00000000000..aa8a9764c35 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/returnInCatch.kt @@ -0,0 +1,11 @@ +tailRecursive fun test(counter : Int, a : Any) : Int { + if (counter == 0) return 0 + + try { + throw Exception() + } catch (e : Exception) { + return test(counter - 1, "no tail") + } +} + +fun box() : String = if (test(3, "test") == 0) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/returnInFinally.kt b/compiler/testData/codegen/box/functions/tail-recursion/returnInFinally.kt new file mode 100644 index 00000000000..b28b36f81b7 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/returnInFinally.kt @@ -0,0 +1,11 @@ +tailRecursive fun test(counter : Int, a : Any) : Int { + if (counter == 0) return 0 + + try { + // do nothing + } finally { + return test(counter - 1, "no tail") + } +} + +fun box() : String = if (test(3, "test") == 0) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/returnInIfInFinally.kt b/compiler/testData/codegen/box/functions/tail-recursion/returnInIfInFinally.kt new file mode 100644 index 00000000000..e1e49b367dc --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/returnInIfInFinally.kt @@ -0,0 +1,15 @@ +tailRecursive fun test(counter : Int, a : Any) : Int { + if (counter == 0) return 0 + + try { + // do nothing + } finally { + if (counter > 0) { + return test(counter - 1, "no tail") + } + } + + return -1 +} + +fun box() : String = if (test(3, "test") == 0) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/returnInTry.kt b/compiler/testData/codegen/box/functions/tail-recursion/returnInTry.kt new file mode 100644 index 00000000000..51c726731e4 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/returnInTry.kt @@ -0,0 +1,11 @@ +tailRecursive fun test(counter : Int, a : Any) : Int { + if (counter == 0) return 0 + + try { + return test(counter - 1, "no tail") + } catch (any : Throwable) { + return -1 + } +} + +fun box() : String = if (test(3, "test") == 0) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/simpleBlock.kt b/compiler/testData/codegen/box/functions/tail-recursion/simpleBlock.kt new file mode 100644 index 00000000000..625d60e62a0 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/simpleBlock.kt @@ -0,0 +1,11 @@ +tailRecursive fun test(x : Int, a : Any) : Int = + if (x == 1) { + test(x - 1, "no tail") + 1 + test(x - 1, "no tail") + } else if (x > 0) { + test(x - 1, "tail") + } else { + 0 + } + +fun box() : String = if (test(1000000, "test") == 1) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/simpleReturn.kt b/compiler/testData/codegen/box/functions/tail-recursion/simpleReturn.kt new file mode 100644 index 00000000000..5a758179b4f --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/simpleReturn.kt @@ -0,0 +1,11 @@ +tailRecursive fun test(x : Int, z : Any) : Int { + if (x == 10) { + return 1 + test(x - 1, "no tail") + } + if (x > 0) { + return test(x - 1, "tail") + } + return 0 +} + +fun box() : String = if (test(1000000, "test") == 1) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/simpleReturnWithElse.kt b/compiler/testData/codegen/box/functions/tail-recursion/simpleReturnWithElse.kt new file mode 100644 index 00000000000..f8cb053c359 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/simpleReturnWithElse.kt @@ -0,0 +1,12 @@ +tailRecursive fun test(x : Int, a : Any) : Int { + if (x == 0) { + return 0 + } else if (x == 10) { + test(0, "no tail") + return 1 + test(x - 1, "no tail") + } else { + return test(x - 1, "tail") + } +} + +fun box() : String = if (test(1000000, "test") == 1) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/thisReferences.kt b/compiler/testData/codegen/box/functions/tail-recursion/thisReferences.kt new file mode 100644 index 00000000000..5fb31ea106d --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/thisReferences.kt @@ -0,0 +1,23 @@ +class A { + tailRecursive fun f1(c : Int, x : Any) { + if (c > 0) { + this.f1(c - 1, "tail") + } + } + + tailRecursive fun f2(c : Int, x : Any) { + if (c > 0) { + f2(c - 1, "tail 108") + } + } + + tailRecursive fun f3(a : A, x : Any) { + a.f3(a, "no tail") // non-tail recursion, could be potentially resolved by condition if (a == this) f3() else a.f3() + } +} + +fun box() : String { + A().f1(1000000, "test") + A().f2(1000000, "test") + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/unitBlocks.kt b/compiler/testData/codegen/box/functions/tail-recursion/unitBlocks.kt new file mode 100644 index 00000000000..d5555bc56c2 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/unitBlocks.kt @@ -0,0 +1,21 @@ +tailRecursive fun test(x : Int, e : Any) : Unit { + if (x == 1) { + test(x - 1, "tail") + } else if (x == 2) { + test(x - 1, "tail") + return + } else if (x == 3) { + test(x - 1, "no tail") + if (x == 3) { + test(x - 1, "tail") + } + return + } else if (x > 0) { + test(x - 1, "tail") + } +} + +fun box() : String { + test(1000000, "test") + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/whenWithCondition.kt b/compiler/testData/codegen/box/functions/tail-recursion/whenWithCondition.kt new file mode 100644 index 00000000000..3dde2c475e8 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/whenWithCondition.kt @@ -0,0 +1,8 @@ +tailRecursive fun withWhen(counter : Int, x : Any) : Int = + when (counter) { + 0 -> counter + 50 -> 1 + withWhen(counter - 1, "no tail") + else -> withWhen(counter - 1, "tail") + } + +fun box() : String = if (withWhen(100000, "test") == 1) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/whenWithInRange.kt b/compiler/testData/codegen/box/functions/tail-recursion/whenWithInRange.kt new file mode 100644 index 00000000000..8a03f684c80 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/whenWithInRange.kt @@ -0,0 +1,11 @@ +tailRecursive fun withWhen(counter : Int, d : Any, x : Any) : Int = + when (counter) { + 0 -> counter + 1, 2 -> withWhen(counter - 1, "1,2", "tail") + in 3..49 -> withWhen(counter - 1, "3..49", "tail") + 50 -> 1 + withWhen(counter - 1, "50", "no tail") + !in 0..50 -> withWhen(counter - 1, "!0..50", "tail") + else -> withWhen(counter - 1, "else", "tail") + } + +fun box() : String = if (withWhen(100000, "test", "test") == 1) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tail-recursion/whenWithIs.kt b/compiler/testData/codegen/box/functions/tail-recursion/whenWithIs.kt new file mode 100644 index 00000000000..39f5cb3a69f --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/whenWithIs.kt @@ -0,0 +1,15 @@ +tailRecursive fun withWhen(counter : Int, d : Any, x : Any) : Int = + if (counter == 0) { + 0 + } + else if (counter == 5) { + withWhen(counter - 1, 999, "tail") + } + else + when (d) { + is String -> withWhen(counter - 1, "is String", "tail") + is Number -> withWhen(counter, "is Number", "tail") + else -> throw IllegalStateException() + } + +fun box() : String = if (withWhen(100000, "test", "test") == 0) "OK" else "FAIL" diff --git a/compiler/testData/codegen/box/functions/tail-recursion/whenWithoutCondition.kt b/compiler/testData/codegen/box/functions/tail-recursion/whenWithoutCondition.kt new file mode 100644 index 00000000000..161d41c62e2 --- /dev/null +++ b/compiler/testData/codegen/box/functions/tail-recursion/whenWithoutCondition.kt @@ -0,0 +1,9 @@ +tailRecursive fun withWhen2(counter : Int, x : Any) : Int = + when { + counter == 0 -> counter + counter == 50 -> 1 + withWhen2(counter - 1, "no tail") + withWhen2(0, "no tail") == 0 -> withWhen2(counter - 1, "tail") + else -> 1 + } + +fun box() : String = if (withWhen2(100000, "test") == 1) "OK" else "FAIL" \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/checkers/AbstractTailRecursionTest.java b/compiler/tests/org/jetbrains/jet/checkers/AbstractTailRecursionTest.java new file mode 100644 index 00000000000..407bd8ed18f --- /dev/null +++ b/compiler/tests/org/jetbrains/jet/checkers/AbstractTailRecursionTest.java @@ -0,0 +1,180 @@ +/* + * Copyright 2010-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.checkers; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Predicates; +import com.google.common.collect.Lists; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.ConfigurationKind; +import org.jetbrains.jet.cli.jvm.compiler.CliLightClassGenerationSupport; +import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment; +import org.jetbrains.jet.lang.resolve.calls.TailRecursionKind; +import org.jetbrains.jet.lang.descriptors.CallableDescriptor; +import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.BindingTrace; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; +import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM; +import org.jetbrains.jet.lang.resolve.lazy.KotlinTestWithEnvironment; +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public abstract class AbstractTailRecursionTest extends KotlinTestWithEnvironment { + + @Override + protected JetCoreEnvironment createEnvironment() { + return createEnvironmentWithMockJdk(ConfigurationKind.JDK_AND_ANNOTATIONS); + } + + public void doTest(@NotNull String testFile) throws IOException { + JetFile file = JetPsiFactory.createFile(getProject(), FileUtil.loadFile(new File(testFile), true)); + List files = new ArrayList(Collections.singleton(file)); + + final BindingTrace trace = CliLightClassGenerationSupport.getInstanceForCli(getProject()).getTrace(); + assertNotNull("No binding trace found for test", trace); + + AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( + getProject(), files, trace, + Collections.emptyList(), Predicates.alwaysTrue(), false).getBindingContext(); + + file.acceptChildren(new JetTreeVisitor() { + + @Override + public Void visitNamedFunction(@NotNull JetNamedFunction function, @Nullable Data outerData) { + SimpleFunctionDescriptor descriptor = trace.get(BindingContext.FUNCTION, function); + assert descriptor != null : "can't get function descriptor from binding by function declaration node"; + + Data data = new Data(descriptor); + super.visitNamedFunction(function, data); + + if (data.isTail) { + List calls = trace.get(BindingContext.FUNCTION_RECURSIVE_CALL_EXPRESSIONS, descriptor); + if (calls == null) { + calls = Collections.emptyList(); + } + + List detectedRecursions = new ArrayList(calls); + List expectedRecursions = new ArrayList(data.visitedCalls); + + Collections.sort(detectedRecursions, new CallComparator()); + Collections.sort(expectedRecursions, new CallComparator()); + + assertEquals( + "Bad detected tail recursions list for " + descriptor, + stringListOfCallExpressions(expectedRecursions), + stringListOfCallExpressions(detectedRecursions) + ); + } + + return null; + } + + @Override + public Void visitCallExpression(@NotNull JetCallExpression expression, @Nullable Data data) { + if (data != null && data.isTail) { + ResolvedCall call = + trace.get(BindingContext.RESOLVED_CALL, expression.getCalleeExpression()); + + assert call != null : "call node is not yet resolved"; + if (data.functionDescriptor.equals(call.getCandidateDescriptor())) { + JetValueArgumentList argumentList = expression.getValueArgumentList(); + assert argumentList != null : "function call have no arguments list"; + + checkCall(data.functionDescriptor, expression, argumentList, trace); + data.visitedCalls.add(expression); + } + } + + super.visitCallExpression(expression, data); + return null; + } + }, null); + } + + private static String stringListOfCallExpressions(List expectedRecursions) { + return Joiner.on(",\n").skipNulls().join(Lists.transform(expectedRecursions, new CallExpressionToText())); + } + + private static class Data { + public final SimpleFunctionDescriptor functionDescriptor; + public final boolean isTail; + public final List visitedCalls = new ArrayList(); + + private Data(@NotNull SimpleFunctionDescriptor descriptor) { + functionDescriptor = descriptor; + isTail = KotlinBuiltIns.getInstance().isTailRecursive(descriptor); + } + } + + private static void checkCall( + SimpleFunctionDescriptor functionDescriptor, + JetCallExpression expression, + JetValueArgumentList argumentList, + BindingTrace trace + ) { + int size = argumentList.getArguments().size(); + boolean shouldBeTail = size == 0 || isLastArgumentTail(argumentList.getArguments()); + TailRecursionKind status = trace.get(BindingContext.TAIL_RECURSION_CALL, expression); + assertNotNull("Call is not checked for tail recursion", status); + assertEquals("Tail-recursion detection failed for " + functionDescriptor.getName().asString() + " at " + expression.getText(), + shouldBeTail, status.isDoGenerateTailRecursion()); + } + + private static boolean isLastArgumentTail(List arguments) { + JetValueArgument lastArgument = arguments.get(arguments.size() - 1); + JetExpression expression = lastArgument.getArgumentExpression(); + if (expression instanceof JetStringTemplateExpression) { + JetStringTemplateEntry[] entries = ((JetStringTemplateExpression) expression).getEntries(); + StringBuilder sb = new StringBuilder(); + for (JetStringTemplateEntry entry : entries) { + sb.append(entry.getText()); + } + + return !sb.toString().trim().equals("no tail"); + } + + return true; + } + + private static class CallComparator implements Comparator { + @Override + public int compare(@NotNull JetCallExpression o1, @NotNull JetCallExpression o2) { + return o1.getTextOffset() - o2.getTextOffset(); + } + } + + private static class CallExpressionToText implements Function { + @Override + public String apply(JetCallExpression input) { + if (input == null) return null; + return ("\"" + input.getText().replace("\"", "\\\"").trim() + "\""); + } + } +} diff --git a/compiler/tests/org/jetbrains/jet/checkers/TailRecursionDetectorTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/TailRecursionDetectorTestGenerated.java new file mode 100644 index 00000000000..20e4afcdff3 --- /dev/null +++ b/compiler/tests/org/jetbrains/jet/checkers/TailRecursionDetectorTestGenerated.java @@ -0,0 +1,149 @@ +/* + * Copyright 2010-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.checkers; + +import junit.framework.Assert; +import junit.framework.Test; +import junit.framework.TestSuite; + +import java.io.File; +import java.util.regex.Pattern; +import org.jetbrains.jet.JetTestUtils; +import org.jetbrains.jet.test.InnerTestClasses; +import org.jetbrains.jet.test.TestMetadata; + +import org.jetbrains.jet.checkers.AbstractTailRecursionTest; + +/** This class is generated by {@link org.jetbrains.jet.generators.tests.GenerateTests}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("compiler/testData/codegen/box/functions/tail-recursion") +public class TailRecursionDetectorTestGenerated extends AbstractTailRecursionTest { + public void testAllFilesPresentInTail_recursion() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/codegen/box/functions/tail-recursion"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("defaultArgs.kt") + public void testDefaultArgs() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/defaultArgs.kt"); + } + + @TestMetadata("insideElvis.kt") + public void testInsideElvis() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/insideElvis.kt"); + } + + @TestMetadata("labeledThisReferences.kt") + public void testLabeledThisReferences() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/labeledThisReferences.kt"); + } + + @TestMetadata("loops.kt") + public void testLoops() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/loops.kt"); + } + + @TestMetadata("multilevelBlocks.kt") + public void testMultilevelBlocks() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/multilevelBlocks.kt"); + } + + @TestMetadata("realIteratorFoldl.kt") + public void testRealIteratorFoldl() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/realIteratorFoldl.kt"); + } + + @TestMetadata("realStringEscape.kt") + public void testRealStringEscape() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/realStringEscape.kt"); + } + + @TestMetadata("realStringRepeat.kt") + public void testRealStringRepeat() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/realStringRepeat.kt"); + } + + @TestMetadata("recursiveInnerFunction.kt") + public void testRecursiveInnerFunction() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/recursiveInnerFunction.kt"); + } + + @TestMetadata("returnInCatch.kt") + public void testReturnInCatch() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/returnInCatch.kt"); + } + + @TestMetadata("returnInFinally.kt") + public void testReturnInFinally() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/returnInFinally.kt"); + } + + @TestMetadata("returnInIfInFinally.kt") + public void testReturnInIfInFinally() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/returnInIfInFinally.kt"); + } + + @TestMetadata("returnInTry.kt") + public void testReturnInTry() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/returnInTry.kt"); + } + + @TestMetadata("simpleBlock.kt") + public void testSimpleBlock() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/simpleBlock.kt"); + } + + @TestMetadata("simpleReturn.kt") + public void testSimpleReturn() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/simpleReturn.kt"); + } + + @TestMetadata("simpleReturnWithElse.kt") + public void testSimpleReturnWithElse() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/simpleReturnWithElse.kt"); + } + + @TestMetadata("thisReferences.kt") + public void testThisReferences() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/thisReferences.kt"); + } + + @TestMetadata("unitBlocks.kt") + public void testUnitBlocks() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/unitBlocks.kt"); + } + + @TestMetadata("whenWithCondition.kt") + public void testWhenWithCondition() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/whenWithCondition.kt"); + } + + @TestMetadata("whenWithInRange.kt") + public void testWhenWithInRange() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/whenWithInRange.kt"); + } + + @TestMetadata("whenWithIs.kt") + public void testWhenWithIs() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/whenWithIs.kt"); + } + + @TestMetadata("whenWithoutCondition.kt") + public void testWhenWithoutCondition() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/whenWithoutCondition.kt"); + } + +} diff --git a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java index 98f0aafd712..fd7eb26d2e5 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java @@ -2361,7 +2361,7 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } @TestMetadata("compiler/testData/codegen/box/functions") - @InnerTestClasses({Functions.Invoke.class, Functions.LocalFunctions.class}) + @InnerTestClasses({Functions.Invoke.class, Functions.LocalFunctions.class, Functions.Tail_recursion.class}) public static class Functions extends AbstractBlackBoxCodegenTest { public void testAllFilesPresentInFunctions() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/codegen/box/functions"), Pattern.compile("^(.+)\\.kt$"), true); @@ -2623,11 +2623,130 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } + @TestMetadata("compiler/testData/codegen/box/functions/tail-recursion") + public static class Tail_recursion extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInTail_recursion() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/codegen/box/functions/tail-recursion"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("defaultArgs.kt") + public void testDefaultArgs() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/defaultArgs.kt"); + } + + @TestMetadata("insideElvis.kt") + public void testInsideElvis() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/insideElvis.kt"); + } + + @TestMetadata("labeledThisReferences.kt") + public void testLabeledThisReferences() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/labeledThisReferences.kt"); + } + + @TestMetadata("loops.kt") + public void testLoops() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/loops.kt"); + } + + @TestMetadata("multilevelBlocks.kt") + public void testMultilevelBlocks() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/multilevelBlocks.kt"); + } + + @TestMetadata("realIteratorFoldl.kt") + public void testRealIteratorFoldl() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/realIteratorFoldl.kt"); + } + + @TestMetadata("realStringEscape.kt") + public void testRealStringEscape() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/realStringEscape.kt"); + } + + @TestMetadata("realStringRepeat.kt") + public void testRealStringRepeat() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/realStringRepeat.kt"); + } + + @TestMetadata("recursiveInnerFunction.kt") + public void testRecursiveInnerFunction() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/recursiveInnerFunction.kt"); + } + + @TestMetadata("returnInCatch.kt") + public void testReturnInCatch() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/returnInCatch.kt"); + } + + @TestMetadata("returnInFinally.kt") + public void testReturnInFinally() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/returnInFinally.kt"); + } + + @TestMetadata("returnInIfInFinally.kt") + public void testReturnInIfInFinally() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/returnInIfInFinally.kt"); + } + + @TestMetadata("returnInTry.kt") + public void testReturnInTry() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/returnInTry.kt"); + } + + @TestMetadata("simpleBlock.kt") + public void testSimpleBlock() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/simpleBlock.kt"); + } + + @TestMetadata("simpleReturn.kt") + public void testSimpleReturn() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/simpleReturn.kt"); + } + + @TestMetadata("simpleReturnWithElse.kt") + public void testSimpleReturnWithElse() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/simpleReturnWithElse.kt"); + } + + @TestMetadata("thisReferences.kt") + public void testThisReferences() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/thisReferences.kt"); + } + + @TestMetadata("unitBlocks.kt") + public void testUnitBlocks() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/unitBlocks.kt"); + } + + @TestMetadata("whenWithCondition.kt") + public void testWhenWithCondition() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/whenWithCondition.kt"); + } + + @TestMetadata("whenWithInRange.kt") + public void testWhenWithInRange() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/whenWithInRange.kt"); + } + + @TestMetadata("whenWithIs.kt") + public void testWhenWithIs() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/whenWithIs.kt"); + } + + @TestMetadata("whenWithoutCondition.kt") + public void testWhenWithoutCondition() throws Exception { + doTest("compiler/testData/codegen/box/functions/tail-recursion/whenWithoutCondition.kt"); + } + + } + public static Test innerSuite() { TestSuite suite = new TestSuite("Functions"); suite.addTestSuite(Functions.class); suite.addTestSuite(Invoke.class); suite.addTestSuite(LocalFunctions.class); + suite.addTestSuite(Tail_recursion.class); return suite; } } diff --git a/generators/src/org/jetbrains/jet/generators/tests/GenerateTests.java b/generators/src/org/jetbrains/jet/generators/tests/GenerateTests.java index 024011b23fe..bb7e30ce558 100644 --- a/generators/src/org/jetbrains/jet/generators/tests/GenerateTests.java +++ b/generators/src/org/jetbrains/jet/generators/tests/GenerateTests.java @@ -22,6 +22,7 @@ import org.jetbrains.jet.cfg.AbstractControlFlowTest; import org.jetbrains.jet.checkers.AbstractDiagnosticsTestWithEagerResolve; import org.jetbrains.jet.checkers.AbstractJetJsCheckerTest; import org.jetbrains.jet.checkers.AbstractJetPsiCheckerTest; +import org.jetbrains.jet.checkers.AbstractTailRecursionTest; import org.jetbrains.jet.cli.AbstractKotlincExecutableTest; import org.jetbrains.jet.codegen.AbstractBytecodeTextTest; import org.jetbrains.jet.codegen.AbstractCheckLocalVariablesTableTest; @@ -220,6 +221,13 @@ public class GenerateTests { testModel("compiler/testData/renderer") ); + generateTest( + "compiler/tests", + "TailRecursionDetectorTestGenerated", + AbstractTailRecursionTest.class, + testModel("compiler/testData/codegen/box/functions/tail-recursion") + ); + generateTest("compiler/tests", "LazyResolveTestGenerated", AbstractLazyResolveTest.class,