diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java index 0381e3d7140..f962862aea2 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetFlowInformationProvider.java @@ -24,19 +24,23 @@ import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.cfg.pseudocode.*; -import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.*; +import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.Edges; +import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.InstructionAnalyzeStrategy; +import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.InstructionDataAnalyzeStrategy; import org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableInitState; import org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableUseState; +import org.jetbrains.jet.lang.cfg.pseudocode.*; import org.jetbrains.jet.lang.descriptors.*; -import org.jetbrains.jet.lang.diagnostics.DiagnosticFactory; import org.jetbrains.jet.lang.diagnostics.Diagnostic; +import org.jetbrains.jet.lang.diagnostics.DiagnosticFactory; import org.jetbrains.jet.lang.diagnostics.Errors; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.BindingContextUtils; import org.jetbrains.jet.lang.resolve.BindingTrace; import org.jetbrains.jet.lang.resolve.DescriptorUtils; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; +import org.jetbrains.jet.lang.resolve.calls.tail.TailRecursionKind; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.jet.lexer.JetTokens; @@ -50,6 +54,10 @@ import static org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableUseStat import static org.jetbrains.jet.lang.diagnostics.Errors.*; import static org.jetbrains.jet.lang.resolve.BindingContext.CAPTURED_IN_CLOSURE; import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE; +import static org.jetbrains.jet.lang.resolve.BindingContext.*; +import static org.jetbrains.jet.lang.resolve.calls.tail.TailRecursionKind.IN_TRY; +import static org.jetbrains.jet.lang.resolve.calls.tail.TailRecursionKind.MIGHT_BE; +import static org.jetbrains.jet.lang.resolve.calls.tail.TailRecursionKind.NON_TAIL; import static org.jetbrains.jet.lang.types.TypeUtils.noExpectedType; public class JetFlowInformationProvider { @@ -64,7 +72,7 @@ public class JetFlowInformationProvider { @NotNull BindingTrace trace, @NotNull Pseudocode pseudocode ) { - subroutine = declaration; + this.subroutine = declaration; this.trace = trace; this.pseudocode = pseudocode; } @@ -94,7 +102,7 @@ public class JetFlowInformationProvider { } checkDefiniteReturn(expectedReturnType); - checkDefiniteReturnInLocalFunctions(); + checkLocalFunctions(); if (isLocalObject) return; @@ -106,6 +114,8 @@ public class JetFlowInformationProvider { markUnusedVariables(); markUnusedLiteralsInBlock(); + + markTailCalls(); } private void collectReturnExpressions(@NotNull final Collection returnedExpressions) { @@ -168,7 +178,7 @@ public class JetFlowInformationProvider { } } - private void checkDefiniteReturnInLocalFunctions() { + private void checkLocalFunctions() { for (LocalFunctionDeclarationInstruction localDeclarationInstruction : pseudocode.getLocalDeclarations()) { JetElement element = localDeclarationInstruction.getElement(); if (element instanceof JetNamedFunction) { @@ -178,7 +188,9 @@ public class JetFlowInformationProvider { JetFlowInformationProvider providerForLocalDeclaration = new JetFlowInformationProvider(localFunction, trace, localDeclarationInstruction.getBody()); + providerForLocalDeclaration.checkDefiniteReturn(expectedType != null ? expectedType : NO_EXPECTED_TYPE); + providerForLocalDeclaration.markTailCalls(); } } } @@ -610,6 +622,117 @@ public class JetFlowInformationProvider { } //////////////////////////////////////////////////////////////////////////////// +// Tail calls + + public void markTailCalls() { + final DeclarationDescriptor subroutineDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, subroutine); + if (!(subroutineDescriptor instanceof FunctionDescriptor)) return; + if (!KotlinBuiltIns.getInstance().isTailRecursive(subroutineDescriptor)) return; + + // finally blocks are copied which leads to multiple diagnostics reported on one instruction + class KindAndCall { + TailRecursionKind kind; + ResolvedCall call; + + KindAndCall(TailRecursionKind kind, ResolvedCall call) { + this.kind = kind; + this.call = call; + } + } + final Map calls = new HashMap(); + PseudocodeTraverser.traverse( + pseudocode, + FORWARD, + new InstructionAnalyzeStrategy() { + @Override + public void execute(@NotNull Instruction instruction) { + if (!(instruction instanceof CallInstruction)) return; + CallInstruction callInstruction = (CallInstruction) instruction; + + ResolvedCall resolvedCall = trace.get(RESOLVED_CALL, callInstruction.getElement()); + if (resolvedCall == null) return; + + // is this a recursive call? + if (!resolvedCall.getResultingDescriptor().getOriginal().equals(subroutineDescriptor)) return; + + JetElement element = callInstruction.getElement(); + //noinspection unchecked + JetExpression parent = PsiTreeUtil.getParentOfType( + element, + JetTryExpression.class, JetFunction.class, JetClassInitializer.class + ); + + if (parent instanceof JetTryExpression) { + // We do not support tail calls Collections.singletonMap() try-catch-finally, for simplicity of the mental model + // very few cases there would be real tail-calls, and it's often not so easy for the user to see why + calls.put(element, new KindAndCall(IN_TRY, resolvedCall)); + return; + } + + boolean isTail = PseudocodeTraverser.traverseFollowingInstructions( + callInstruction, + new HashSet(), + FORWARD, + new TailRecursionDetector(subroutine, callInstruction) + ); + + TailRecursionKind kind = isTail ? MIGHT_BE : NON_TAIL; + + KindAndCall kindAndCall = calls.get(element); + calls.put(element, + new KindAndCall( + combineKinds(kind, kindAndCall == null ? null : kindAndCall.kind), + resolvedCall + ) + ); + } + } + ); + for (Map.Entry entry : calls.entrySet()) { + JetElement element = entry.getKey(); + KindAndCall kindAndCall = entry.getValue(); + switch (kindAndCall.kind) { + case MIGHT_BE: + case IN_RETURN: + trace.record(TAIL_RECURSION_CALL, kindAndCall.call, TailRecursionKind.IN_RETURN); + trace.record(BindingContext.HAS_TAIL_CALLS, (FunctionDescriptor) subroutineDescriptor); + break; + case IN_TRY: + trace.report(Errors.TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED.on(element)); + break; + case NON_TAIL: + trace.report(Errors.NON_TAIL_RECURSIVE_CALL.on(element)); + break; + } + + } + } + + private static TailRecursionKind combineKinds(TailRecursionKind kind, @Nullable TailRecursionKind existingKind) { + TailRecursionKind resultingKind; + if (existingKind == null || existingKind == kind) { + resultingKind = kind; + } + else { + if (check(kind, existingKind, IN_TRY, MIGHT_BE)) { + resultingKind = IN_TRY; + } + else if (check(kind, existingKind, IN_TRY, NON_TAIL)) { + resultingKind = IN_TRY; + } + else { + // MIGHT_BE, NON_TAIL + resultingKind = NON_TAIL; + } + } + return resultingKind; + } + + private static boolean check(Object a, Object b, Object x, Object y) { + return (a == x && b == y) || (a == y && b == x); + } + + //////////////////////////////////////////////////////////////////////////////// // Utility classes and methods /** diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/TailRecursionDetector.java b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/TailRecursionDetector.java new file mode 100644 index 00000000000..7bde7418b86 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/TailRecursionDetector.java @@ -0,0 +1,66 @@ +/* + * 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.lang.cfg; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.cfg.pseudocode.*; +import org.jetbrains.jet.lang.psi.JetElement; + +public class TailRecursionDetector extends InstructionVisitorWithResult implements PseudocodeTraverser.InstructionHandler { + private final JetElement subroutine; + private final Instruction start; + + public TailRecursionDetector(@NotNull JetElement subroutine, @NotNull Instruction start) { + this.subroutine = subroutine; + this.start = start; + } + + @Override + public boolean handle(@NotNull Instruction instruction) { + return instruction == start || instruction.accept(this); + } + + @Override + public Boolean visitInstruction(Instruction instruction) { + return false; + } + + @Override + public Boolean visitSubroutineExit(SubroutineExitInstruction instruction) { + return !instruction.isError() && instruction.getSubroutine() == subroutine; + } + + @Override + public Boolean visitSubroutineSink(SubroutineSinkInstruction instruction) { + return instruction.getSubroutine() == subroutine; + } + + @Override + public Boolean visitJump(AbstractJumpInstruction instruction) { + return true; + } + + @Override + public Boolean visitThrowExceptionInstruction(ThrowExceptionInstruction instruction) { + return false; + } + + @Override + public Boolean visitMarkInstruction(MarkInstruction instruction) { + return true; + } +} diff --git a/compiler/testData/cfg/tailCalls/finally.instructions b/compiler/testData/cfg/tailCalls/finally.instructions new file mode 100644 index 00000000000..8a7f98d1e34 --- /dev/null +++ b/compiler/testData/cfg/tailCalls/finally.instructions @@ -0,0 +1,35 @@ +== test == +tailRecursive fun test() : Int { + try { + // do nothing + } finally { + test() + } +} +--------------------- +L0: + + mark({ try { // do nothing } finally { test() } }) + mark(try { // do nothing } finally { test() }) + jmp?(L2 [onExceptionToFinallyBlock]) NEXT:[mark({ test() }), mark({ // do nothing })] + mark({ // do nothing }) + read (Unit) + jmp(L3 [skipFinallyToErrorBlock]) NEXT:[mark({ test() })] +L2 [onExceptionToFinallyBlock]: +L4 [start finally]: + mark({ test() }) PREV:[jmp?(L2 [onExceptionToFinallyBlock])] + mark(test()) + call(test, test) +L5 [finish finally]: + jmp(error) NEXT:[] +L3 [skipFinallyToErrorBlock]: + mark({ test() }) PREV:[jmp(L3 [skipFinallyToErrorBlock])] + mark(test()) + call(test, test) +L1: + NEXT:[] +error: + PREV:[jmp(error)] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/tailCalls/finally.kt b/compiler/testData/cfg/tailCalls/finally.kt new file mode 100644 index 00000000000..6b1b19b9c6c --- /dev/null +++ b/compiler/testData/cfg/tailCalls/finally.kt @@ -0,0 +1,7 @@ +tailRecursive fun test() : Int { + try { + // do nothing + } finally { + test() + } +} diff --git a/compiler/testData/cfg/tailCalls/finallyWithReturn.instructions b/compiler/testData/cfg/tailCalls/finallyWithReturn.instructions new file mode 100644 index 00000000000..a3248bf0cda --- /dev/null +++ b/compiler/testData/cfg/tailCalls/finallyWithReturn.instructions @@ -0,0 +1,37 @@ +== test == +tailRecursive fun test() : Int { + try { + // do nothing + } finally { + return test() + } +} +--------------------- +L0: + + mark({ try { // do nothing } finally { return test() } }) + mark(try { // do nothing } finally { return test() }) + jmp?(L2 [onExceptionToFinallyBlock]) NEXT:[mark({ return test() }), mark({ // do nothing })] + mark({ // do nothing }) + read (Unit) + jmp(L3 [skipFinallyToErrorBlock]) NEXT:[mark({ return test() })] +L2 [onExceptionToFinallyBlock]: +L4 [start finally]: + mark({ return test() }) PREV:[jmp?(L2 [onExceptionToFinallyBlock])] + mark(test()) + call(test, test) + ret(*) L1 NEXT:[] +L5 [finish finally]: +- jmp(error) NEXT:[] PREV:[] +L3 [skipFinallyToErrorBlock]: + mark({ return test() }) PREV:[jmp(L3 [skipFinallyToErrorBlock])] + mark(test()) + call(test, test) + ret(*) L1 +L1: + NEXT:[] PREV:[ret(*) L1, ret(*) L1] +error: + PREV:[] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/tailCalls/finallyWithReturn.kt b/compiler/testData/cfg/tailCalls/finallyWithReturn.kt new file mode 100644 index 00000000000..d5d8d1f78c4 --- /dev/null +++ b/compiler/testData/cfg/tailCalls/finallyWithReturn.kt @@ -0,0 +1,7 @@ +tailRecursive fun test() : Int { + try { + // do nothing + } finally { + return test() + } +} diff --git a/compiler/testData/cfg/tailCalls/sum.instructions b/compiler/testData/cfg/tailCalls/sum.instructions new file mode 100644 index 00000000000..0fdf0fdf1d1 --- /dev/null +++ b/compiler/testData/cfg/tailCalls/sum.instructions @@ -0,0 +1,46 @@ +== sum == +tailRecursive fun sum(x: Long, sum: Long): Long { + if (x == 0.toLong()) return sum + return sum(x - 1, sum + x) +} +--------------------- +L0: + + v(x: Long) + w(x) + v(sum: Long) + w(sum) + mark({ if (x == 0.toLong()) return sum return sum(x - 1, sum + x) }) + mark(if (x == 0.toLong()) return sum) + mark(x == 0.toLong()) + r(x) + mark(0.toLong()) + mark(toLong()) + r(0) + call(toLong, toLong) + call(x == 0.toLong(), equals) + jf(L2) NEXT:[read (Unit), r(sum)] + r(sum) + ret(*) L1 NEXT:[] +- jmp(L3) NEXT:[mark(sum(x - 1, sum + x))] PREV:[] +L2: + read (Unit) PREV:[jf(L2)] +L3: + mark(sum(x - 1, sum + x)) + mark(x - 1) + r(x) + r(1) + call(-, minus) + mark(sum + x) + r(sum) + r(x) + call(+, plus) + call(sum, sum) + ret(*) L1 +L1: + NEXT:[] PREV:[ret(*) L1, ret(*) L1] +error: + PREV:[] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/tailCalls/sum.kt b/compiler/testData/cfg/tailCalls/sum.kt new file mode 100644 index 00000000000..9585320d039 --- /dev/null +++ b/compiler/testData/cfg/tailCalls/sum.kt @@ -0,0 +1,4 @@ +tailRecursive fun sum(x: Long, sum: Long): Long { + if (x == 0.toLong()) return sum + return sum(x - 1, sum + x) +} \ No newline at end of file diff --git a/compiler/testData/cfg/tailCalls/try.instructions b/compiler/testData/cfg/tailCalls/try.instructions new file mode 100644 index 00000000000..e8a94b1bed8 --- /dev/null +++ b/compiler/testData/cfg/tailCalls/try.instructions @@ -0,0 +1,33 @@ +== foo == +tailRecursive fun foo() { + try { + return foo() + } + catch (e: Throwable) { + } +} +--------------------- +L0: + + mark({ try { return foo() } catch (e: Throwable) { } }) + mark(try { return foo() } catch (e: Throwable) { }) + jmp?(L2 [onException]) NEXT:[v(e: Throwable), mark({ return foo() })] + mark({ return foo() }) + mark(foo()) + call(foo, foo) + ret(*) L1 NEXT:[] +- jmp(L3 [afterCatches]) NEXT:[] PREV:[] +L2 [onException]: + v(e: Throwable) PREV:[jmp?(L2 [onException])] + w(e) + mark({ }) + read (Unit) + jmp(L3 [afterCatches]) +L1: +L3 [afterCatches]: + NEXT:[] PREV:[ret(*) L1, jmp(L3 [afterCatches])] +error: + PREV:[] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/tailCalls/try.kt b/compiler/testData/cfg/tailCalls/try.kt new file mode 100644 index 00000000000..6e6dc6c08ce --- /dev/null +++ b/compiler/testData/cfg/tailCalls/try.kt @@ -0,0 +1,7 @@ +tailRecursive fun foo() { + try { + return foo() + } + catch (e: Throwable) { + } +} diff --git a/compiler/testData/cfg/tailCalls/tryCatchFinally.instructions b/compiler/testData/cfg/tailCalls/tryCatchFinally.instructions new file mode 100644 index 00000000000..271b8c8cf64 --- /dev/null +++ b/compiler/testData/cfg/tailCalls/tryCatchFinally.instructions @@ -0,0 +1,48 @@ +== test == +fun test() : Unit { + try { + test() + } catch (any : Exception) { + test() + } finally { + test() + } +} +--------------------- +L0: + + mark({ try { test() } catch (any : Exception) { test() } finally { test() } }) + mark(try { test() } catch (any : Exception) { test() } finally { test() }) + jmp?(L2 [onException]) NEXT:[v(any : Exception), jmp?(L3 [onExceptionToFinallyBlock])] + jmp?(L3 [onExceptionToFinallyBlock]) NEXT:[mark({ test() }), mark({ test() })] + mark({ test() }) + mark(test()) + call(test, test) + jmp(L4 [afterCatches]) NEXT:[jmp(L5 [skipFinallyToErrorBlock])] +L2 [onException]: + v(any : Exception) PREV:[jmp?(L2 [onException])] + w(any) + mark({ test() }) + mark(test()) + call(test, test) + jmp(L4 [afterCatches]) +L4 [afterCatches]: + jmp(L5 [skipFinallyToErrorBlock]) NEXT:[mark({ test() })] PREV:[jmp(L4 [afterCatches]), jmp(L4 [afterCatches])] +L3 [onExceptionToFinallyBlock]: +L6 [start finally]: + mark({ test() }) PREV:[jmp?(L3 [onExceptionToFinallyBlock])] + mark(test()) + call(test, test) +L7 [finish finally]: + jmp(error) NEXT:[] +L5 [skipFinallyToErrorBlock]: + mark({ test() }) PREV:[jmp(L5 [skipFinallyToErrorBlock])] + mark(test()) + call(test, test) +L1: + NEXT:[] +error: + PREV:[jmp(error)] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/tailCalls/tryCatchFinally.kt b/compiler/testData/cfg/tailCalls/tryCatchFinally.kt new file mode 100644 index 00000000000..e6e41d35784 --- /dev/null +++ b/compiler/testData/cfg/tailCalls/tryCatchFinally.kt @@ -0,0 +1,9 @@ +fun test() : Unit { + try { + test() + } catch (any : Exception) { + test() + } finally { + test() + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/functions/tailRecursion/sum.kt b/compiler/testData/codegen/box/functions/tailRecursion/sum.kt new file mode 100644 index 00000000000..084de2c706d --- /dev/null +++ b/compiler/testData/codegen/box/functions/tailRecursion/sum.kt @@ -0,0 +1,10 @@ +tailRecursive fun sum(x: Long, sum: Long): Long { + if (x == 0.toLong()) return sum + return sum(x - 1, sum + x) +} + +fun box() : String { + val sum = sum(1000000, 0) + if (sum != 500000500000.toLong()) return "Fail $sum" + return "OK" +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/cfg/ControlFlowTestGenerated.java b/compiler/tests/org/jetbrains/jet/cfg/ControlFlowTestGenerated.java index 7d1c4ea9293..d8b81c294e2 100644 --- a/compiler/tests/org/jetbrains/jet/cfg/ControlFlowTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/cfg/ControlFlowTestGenerated.java @@ -31,7 +31,7 @@ import org.jetbrains.jet.cfg.AbstractControlFlowTest; /** This class is generated by {@link org.jetbrains.jet.generators.tests.GenerateTests}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") @TestMetadata("compiler/testData/cfg") -@InnerTestClasses({ControlFlowTestGenerated.DeadCode.class}) +@InnerTestClasses({ControlFlowTestGenerated.DeadCode.class, ControlFlowTestGenerated.TailCalls.class}) public class ControlFlowTestGenerated extends AbstractControlFlowTest { public void testAllFilesPresentInCfg() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/cfg"), Pattern.compile("^(.+)\\.kt$"), true); @@ -245,10 +245,44 @@ public class ControlFlowTestGenerated extends AbstractControlFlowTest { } + @TestMetadata("compiler/testData/cfg/tailCalls") + public static class TailCalls extends AbstractControlFlowTest { + public void testAllFilesPresentInTailCalls() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/cfg/tailCalls"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("finally.kt") + public void testFinally() throws Exception { + doTest("compiler/testData/cfg/tailCalls/finally.kt"); + } + + @TestMetadata("finallyWithReturn.kt") + public void testFinallyWithReturn() throws Exception { + doTest("compiler/testData/cfg/tailCalls/finallyWithReturn.kt"); + } + + @TestMetadata("sum.kt") + public void testSum() throws Exception { + doTest("compiler/testData/cfg/tailCalls/sum.kt"); + } + + @TestMetadata("try.kt") + public void testTry() throws Exception { + doTest("compiler/testData/cfg/tailCalls/try.kt"); + } + + @TestMetadata("tryCatchFinally.kt") + public void testTryCatchFinally() throws Exception { + doTest("compiler/testData/cfg/tailCalls/tryCatchFinally.kt"); + } + + } + public static Test suite() { TestSuite suite = new TestSuite("ControlFlowTestGenerated"); suite.addTestSuite(ControlFlowTestGenerated.class); suite.addTestSuite(DeadCode.class); + suite.addTestSuite(TailCalls.class); return suite; } } diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index 07e30c1b431..80929a343b6 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -6883,6 +6883,11 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage doTest("compiler/testData/codegen/box/functions/tailRecursion/simpleReturnWithElse.kt"); } + @TestMetadata("sum.kt") + public void testSum() throws Exception { + doTest("compiler/testData/codegen/box/functions/tailRecursion/sum.kt"); + } + @TestMetadata("thisReferences.kt") public void testThisReferences() throws Exception { doTest("compiler/testData/codegen/box/functions/tailRecursion/thisReferences.kt"); diff --git a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java index a297b99e3fb..88120c7fe31 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java @@ -2719,6 +2719,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest("compiler/testData/codegen/box/functions/tailRecursion/simpleReturnWithElse.kt"); } + @TestMetadata("sum.kt") + public void testSum() throws Exception { + doTest("compiler/testData/codegen/box/functions/tailRecursion/sum.kt"); + } + @TestMetadata("thisReferences.kt") public void testThisReferences() throws Exception { doTest("compiler/testData/codegen/box/functions/tailRecursion/thisReferences.kt");