Detecting tail calls through CFA
This commit is contained in:
@@ -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<JetElement> 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<JetElement, KindAndCall> calls = new HashMap<JetElement, KindAndCall>();
|
||||
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<Instruction>(),
|
||||
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<JetElement, KindAndCall> 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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Boolean> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
== test ==
|
||||
tailRecursive fun test() : Int {
|
||||
try {
|
||||
// do nothing
|
||||
} finally {
|
||||
test()
|
||||
}
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
<START>
|
||||
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:[<ERROR>]
|
||||
L3 [skipFinallyToErrorBlock]:
|
||||
mark({ test() }) PREV:[jmp(L3 [skipFinallyToErrorBlock])]
|
||||
mark(test())
|
||||
call(test, test)
|
||||
L1:
|
||||
<END> NEXT:[<SINK>]
|
||||
error:
|
||||
<ERROR> PREV:[jmp(error)]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
@@ -0,0 +1,7 @@
|
||||
tailRecursive fun test() : Int {
|
||||
try {
|
||||
// do nothing
|
||||
} finally {
|
||||
test()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
== test ==
|
||||
tailRecursive fun test() : Int {
|
||||
try {
|
||||
// do nothing
|
||||
} finally {
|
||||
return test()
|
||||
}
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
<START>
|
||||
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:[<END>]
|
||||
L5 [finish finally]:
|
||||
- jmp(error) NEXT:[<ERROR>] PREV:[]
|
||||
L3 [skipFinallyToErrorBlock]:
|
||||
mark({ return test() }) PREV:[jmp(L3 [skipFinallyToErrorBlock])]
|
||||
mark(test())
|
||||
call(test, test)
|
||||
ret(*) L1
|
||||
L1:
|
||||
<END> NEXT:[<SINK>] PREV:[ret(*) L1, ret(*) L1]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
@@ -0,0 +1,7 @@
|
||||
tailRecursive fun test() : Int {
|
||||
try {
|
||||
// do nothing
|
||||
} finally {
|
||||
return test()
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
<START>
|
||||
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:[<END>]
|
||||
- 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:
|
||||
<END> NEXT:[<SINK>] PREV:[ret(*) L1, ret(*) L1]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
@@ -0,0 +1,4 @@
|
||||
tailRecursive fun sum(x: Long, sum: Long): Long {
|
||||
if (x == 0.toLong()) return sum
|
||||
return sum(x - 1, sum + x)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
== foo ==
|
||||
tailRecursive fun foo() {
|
||||
try {
|
||||
return foo()
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
}
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
<START>
|
||||
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:[<END>]
|
||||
- jmp(L3 [afterCatches]) NEXT:[<END>] PREV:[]
|
||||
L2 [onException]:
|
||||
v(e: Throwable) PREV:[jmp?(L2 [onException])]
|
||||
w(e)
|
||||
mark({ })
|
||||
read (Unit)
|
||||
jmp(L3 [afterCatches])
|
||||
L1:
|
||||
L3 [afterCatches]:
|
||||
<END> NEXT:[<SINK>] PREV:[ret(*) L1, jmp(L3 [afterCatches])]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
@@ -0,0 +1,7 @@
|
||||
tailRecursive fun foo() {
|
||||
try {
|
||||
return foo()
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
== test ==
|
||||
fun test() : Unit {
|
||||
try {
|
||||
test()
|
||||
} catch (any : Exception) {
|
||||
test()
|
||||
} finally {
|
||||
test()
|
||||
}
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
<START>
|
||||
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:[<ERROR>]
|
||||
L5 [skipFinallyToErrorBlock]:
|
||||
mark({ test() }) PREV:[jmp(L5 [skipFinallyToErrorBlock])]
|
||||
mark(test())
|
||||
call(test, test)
|
||||
L1:
|
||||
<END> NEXT:[<SINK>]
|
||||
error:
|
||||
<ERROR> PREV:[jmp(error)]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
@@ -0,0 +1,9 @@
|
||||
fun test() : Unit {
|
||||
try {
|
||||
test()
|
||||
} catch (any : Exception) {
|
||||
test()
|
||||
} finally {
|
||||
test()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user