From 1c81102941e00838adfd841d7520262c7c591b4a Mon Sep 17 00:00:00 2001 From: Sergey Mashkov Date: Tue, 26 Nov 2013 18:56:18 +0400 Subject: [PATCH] Tail-call detection support in the front-end --- .../jet/lang/diagnostics/Errors.java | 5 + .../rendering/DefaultErrorMessages.java | 4 +- .../jet/lang/psi/BacktraceVisitorStatus.java | 39 ++++ .../jetbrains/jet/lang/psi/JetPsiUtil.java | 38 +++- .../jetbrains/jet/lang/psi/VisitorData.java | 33 +++ .../jet/lang/resolve/BindingContext.java | 6 + .../resolve/FunctionAnalyzerExtension.java | 5 + ...ilRecursionsFunctionAnalyzerExtension.java | 57 +++++ .../calls/CallResolverExtensionProvider.java | 18 ++ ...ecursiveCallRecorderResolverExtension.java | 53 +++++ .../calls/RecursiveCallResolverExtension.java | 45 ++++ .../calls/TailRecursionDetectorExtension.java | 60 +++++ .../calls/TailRecursionDetectorVisitor.java | 205 ++++++++++++++++++ .../lang/resolve/calls/TailRecursionKind.java | 61 ++++++ .../tailRecursions/functionWithNoTails.kt | 3 + .../functionWithNonTailRecursions.kt | 12 + .../functionWithoutAnnotation.kt | 6 + .../tailRecursions/tailRecursionInFinally.kt | 9 + .../checkers/JetDiagnosticsTestGenerated.java | 31 ++- 19 files changed, 684 insertions(+), 6 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/psi/BacktraceVisitorStatus.java create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/psi/VisitorData.java create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/resolve/TailRecursionsFunctionAnalyzerExtension.java create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/RecursiveCallRecorderResolverExtension.java create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/RecursiveCallResolverExtension.java create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionDetectorExtension.java create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionDetectorVisitor.java create mode 100644 compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionKind.java create mode 100644 compiler/testData/diagnostics/tests/tailRecursions/functionWithNoTails.kt create mode 100644 compiler/testData/diagnostics/tests/tailRecursions/functionWithNonTailRecursions.kt create mode 100644 compiler/testData/diagnostics/tests/tailRecursions/functionWithoutAnnotation.kt create mode 100644 compiler/testData/diagnostics/tests/tailRecursions/tailRecursionInFinally.kt diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java index e26f79a4314..1fbdb7b437f 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java @@ -280,6 +280,8 @@ public interface Errors { DiagnosticFactory0 VALUE_PARAMETER_WITH_NO_TYPE_ANNOTATION = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 NO_TAIL_CALLS_FOUND = DiagnosticFactory0.create(WARNING, NAMED_ELEMENT); + // Named parameters DiagnosticFactory0 DEFAULT_VALUE_NOT_ALLOWED_IN_OVERRIDE = DiagnosticFactory0.create(ERROR, PARAMETER_DEFAULT_VALUE); @@ -334,6 +336,9 @@ public interface Errors { DiagnosticFactory2 FUNCTION_EXPECTED = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 FUNCTION_CALL_EXPECTED = DiagnosticFactory2.create(ERROR, CALL_EXPRESSION); + DiagnosticFactory0 NON_TAIL_RECURSIVE_CALL = DiagnosticFactory0.create(WARNING, CALL_EXPRESSION); + DiagnosticFactory0 TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED = DiagnosticFactory0.create(WARNING, CALL_EXPRESSION); + DiagnosticFactory0 NO_CONSTRUCTOR = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 NOT_A_CLASS = DiagnosticFactory0.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java index 588f2a2301e..89c3137068c 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java @@ -324,6 +324,7 @@ public class DefaultErrorMessages { MAP.put(ILLEGAL_SELECTOR, "Expression ''{0}'' cannot be a selector (occur after a dot)", TO_STRING); + MAP.put(NO_TAIL_CALLS_FOUND, "A function is marked as tail-recursive but no tail calls are found"); MAP.put(VALUE_PARAMETER_WITH_NO_TYPE_ANNOTATION, "A type annotation is required on a value parameter"); MAP.put(BREAK_OR_CONTINUE_OUTSIDE_A_LOOP, "'break' and 'continue' are only allowed inside a loop"); MAP.put(NOT_A_LOOP_LABEL, "The label ''{0}'' does not denote a loop", TO_STRING); @@ -414,7 +415,8 @@ public class DefaultErrorMessages { return hasValueParameters ? "..." : ""; } }); - + MAP.put(NON_TAIL_RECURSIVE_CALL, "Recursive call is not a tail call"); + MAP.put(TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED, "Tail recursion optimization inside try/catch/finally is not supported"); MAP.put(RESULT_TYPE_MISMATCH, "{0} must return {1} but returns {2}", TO_STRING, RENDER_TYPE, RENDER_TYPE); MAP.put(UNSAFE_INFIX_CALL, diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/BacktraceVisitorStatus.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/BacktraceVisitorStatus.java new file mode 100644 index 00000000000..315891ec035 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/BacktraceVisitorStatus.java @@ -0,0 +1,39 @@ +/* + * 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.psi; + +import org.jetbrains.annotations.NotNull; + +public class BacktraceVisitorStatus { + @NotNull + private final T data; + private final boolean abortTrace; + + public BacktraceVisitorStatus(@NotNull T data, boolean abortTrace) { + this.data = data; + this.abortTrace = abortTrace; + } + + @NotNull + public T getData() { + return data; + } + + public boolean isAbortTrace() { + return abortTrace; + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java index 0e13f2382f4..f4a291bbd2e 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java @@ -42,10 +42,7 @@ import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.jet.lexer.JetToken; import org.jetbrains.jet.lexer.JetTokens; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; public class JetPsiUtil { private JetPsiUtil() { @@ -1031,4 +1028,37 @@ public class JetPsiUtil { expression; return (JetToken) elementType; } + + @NotNull + public static T visitUpwardToRoot( + @NotNull PsiElement element, + @NotNull JetVisitor, VisitorData> visitor, + T def + ) { + List track = new ArrayList(); + List view = Collections.unmodifiableList(track); + @NotNull + BacktraceVisitorStatus lastStatus = new BacktraceVisitorStatus(def, true); + VisitorData data = new VisitorData(view); + + do { + track.add(element); + PsiElement parent = element.getParent(); + if (parent instanceof JetElement) { + JetElement jet = (JetElement) parent; + data.last = element; + data.data = lastStatus.getData(); + + BacktraceVisitorStatus status = jet.accept(visitor, data); + if (status == null) { + throw new IllegalStateException("visitor has returned null status"); + } + lastStatus = status; + } + + element = parent; + } while (element != null && !lastStatus.isAbortTrace()); + + return lastStatus.getData(); + } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/VisitorData.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/VisitorData.java new file mode 100644 index 00000000000..d6b5786602f --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/VisitorData.java @@ -0,0 +1,33 @@ +/* + * 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.psi; + +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public final class VisitorData { + public T data; + @NotNull + public final List visitedPath; + public PsiElement last; + + public VisitorData(@NotNull List visitedPath) { + this.visitedPath = visitedPath; + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java index 9c2ea2b73e6..94a608262fe 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java @@ -21,6 +21,7 @@ import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; +import org.jetbrains.jet.lang.resolve.calls.TailRecursionKind; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor; import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptorImpl; @@ -39,6 +40,7 @@ import org.jetbrains.jet.util.slicedmap.*; import java.util.Collection; import java.util.Collections; +import java.util.List; import static org.jetbrains.jet.util.slicedmap.RewritePolicy.DO_NOTHING; @@ -87,6 +89,10 @@ public interface BindingContext { new BasicWritableSlice(DO_NOTHING); WritableSlice> RESOLVED_CALL = new BasicWritableSlice>(DO_NOTHING); + WritableSlice TAIL_RECURSION_CALL = + new BasicWritableSlice(DO_NOTHING, true); + WritableSlice> FUNCTION_RECURSIVE_CALL_EXPRESSIONS = + new BasicWritableSlice>(DO_NOTHING, false); WritableSlice CONSTRAINT_SYSTEM_COMPLETER = new BasicWritableSlice(DO_NOTHING); WritableSlice CALL = new BasicWritableSlice(DO_NOTHING); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/FunctionAnalyzerExtension.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/FunctionAnalyzerExtension.java index 801dc794b0b..49375e15158 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/FunctionAnalyzerExtension.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/FunctionAnalyzerExtension.java @@ -21,9 +21,11 @@ import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor; import org.jetbrains.jet.lang.psi.JetNamedFunction; import org.jetbrains.jet.lang.resolve.extension.InlineAnalyzerExtension; +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import javax.inject.Inject; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -59,6 +61,9 @@ public class FunctionAnalyzerExtension { ((SimpleFunctionDescriptor) functionDescriptor).isInline()) { list.add(InlineAnalyzerExtension.INSTANCE); } + if (KotlinBuiltIns.getInstance().isTailRecursive(functionDescriptor)) { + list.add(TailRecursionsFunctionAnalyzerExtension.INSTANCE); + } return list; } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TailRecursionsFunctionAnalyzerExtension.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TailRecursionsFunctionAnalyzerExtension.java new file mode 100644 index 00000000000..f1ff24f4f70 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TailRecursionsFunctionAnalyzerExtension.java @@ -0,0 +1,57 @@ +/* + * 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.resolve; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; +import org.jetbrains.jet.lang.diagnostics.Errors; +import org.jetbrains.jet.lang.psi.JetCallExpression; +import org.jetbrains.jet.lang.psi.JetNamedFunction; +import org.jetbrains.jet.lang.resolve.calls.TailRecursionKind; + +import java.util.Collections; +import java.util.List; + +import static com.google.common.base.Objects.firstNonNull; + +public class TailRecursionsFunctionAnalyzerExtension implements FunctionAnalyzerExtension.AnalyzerExtension { + + public static final TailRecursionsFunctionAnalyzerExtension INSTANCE = new TailRecursionsFunctionAnalyzerExtension(); + + @Override + public void process( + @NotNull FunctionDescriptor descriptor, @NotNull JetNamedFunction function, @NotNull final BindingTrace trace + ) { + List callExpressions = firstNonNull( + trace.get(BindingContext.FUNCTION_RECURSIVE_CALL_EXPRESSIONS, descriptor), + Collections.emptyList()); + + boolean allNonTailRecursiveCalls = Iterables.all(callExpressions, new Predicate() { + @Override + public boolean apply(JetCallExpression callExpression) { + TailRecursionKind recursionKind = trace.get(BindingContext.TAIL_RECURSION_CALL, callExpression); + return recursionKind == null || !recursionKind.isDoGenerateTailRecursion(); + } + }); + + if (allNonTailRecursiveCalls) { + trace.report(Errors.NO_TAIL_CALLS_FOUND.on(function)); + } + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolverExtensionProvider.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolverExtensionProvider.java index 4405603c85a..a4ebcaf9bb8 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolverExtensionProvider.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolverExtensionProvider.java @@ -16,10 +16,19 @@ package org.jetbrains.jet.lang.resolve.calls; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.CallableDescriptor; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor; +import org.jetbrains.jet.lang.psi.JetCallExpression; +import org.jetbrains.jet.lang.psi.JetPsiUtil; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext; +import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResultsImpl; +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import java.lang.ref.WeakReference; import java.util.*; @@ -77,6 +86,15 @@ public class CallResolverExtensionProvider { extensions.add(new InlineCallResolverExtension(descriptor)); } } + if (isAnnotatedAsTailRecursive(declarationDescriptor)) { + extensions.add(RecursiveCallRecorderResolverExtension.INSTANCE); + extensions.add(TailRecursionDetectorExtension.INSTANCE); + } + // add your extensions here extensions.add(DEFAULT); } + + private static boolean isAnnotatedAsTailRecursive(DeclarationDescriptor descriptor) { + return KotlinBuiltIns.getInstance().isTailRecursive(descriptor); + } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/RecursiveCallRecorderResolverExtension.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/RecursiveCallRecorderResolverExtension.java new file mode 100644 index 00000000000..072622a65d0 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/RecursiveCallRecorderResolverExtension.java @@ -0,0 +1,53 @@ +/* + * 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.resolve.calls; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.CallableDescriptor; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.psi.JetCallExpression; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; +import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResultsImpl; + +import java.util.ArrayList; +import java.util.List; + +public class RecursiveCallRecorderResolverExtension extends RecursiveCallResolverExtension { + + public static final RecursiveCallRecorderResolverExtension INSTANCE = new RecursiveCallRecorderResolverExtension(); + + @Override + protected void runImpl( + @NotNull JetCallExpression callExpression, + @NotNull ResolvedCall resolvedCall, + @NotNull BasicCallResolutionContext context + ) { + DeclarationDescriptor declarationDescriptor = context.scope.getContainingDeclaration(); + + List expressions = + context.trace.get(BindingContext.FUNCTION_RECURSIVE_CALL_EXPRESSIONS, declarationDescriptor); + + if (expressions == null) { + expressions = new ArrayList(2); + context.trace.record(BindingContext.FUNCTION_RECURSIVE_CALL_EXPRESSIONS, declarationDescriptor, expressions); + } + + expressions.add(callExpression); + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/RecursiveCallResolverExtension.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/RecursiveCallResolverExtension.java new file mode 100644 index 00000000000..a9156336901 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/RecursiveCallResolverExtension.java @@ -0,0 +1,45 @@ +/* + * 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.resolve.calls; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.CallableDescriptor; +import org.jetbrains.jet.lang.psi.JetCallExpression; +import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; +import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResultsImpl; + +public abstract class RecursiveCallResolverExtension implements CallResolverExtension { + @Override + public void run( + @NotNull ResolvedCall resolvedCall, @NotNull BasicCallResolutionContext context + ) { + if (isRecursion(resolvedCall, context) && context.call.getCallElement() instanceof JetCallExpression) { + runImpl((JetCallExpression) context.call.getCallElement(), resolvedCall, context); + } + } + + protected abstract void runImpl(@NotNull JetCallExpression callExpression, + @NotNull ResolvedCall resolvedCall, @NotNull BasicCallResolutionContext context); + + private static boolean isRecursion( + @NotNull ResolvedCall resolvedCall, + @NotNull BasicCallResolutionContext context + ) { + return resolvedCall.getResultingDescriptor().getOriginal().equals(context.scope.getContainingDeclaration()); + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionDetectorExtension.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionDetectorExtension.java new file mode 100644 index 00000000000..684be153b3c --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionDetectorExtension.java @@ -0,0 +1,60 @@ +/* + * 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.resolve.calls; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.CallableDescriptor; +import org.jetbrains.jet.lang.diagnostics.Errors; +import org.jetbrains.jet.lang.psi.JetCallExpression; +import org.jetbrains.jet.lang.psi.JetPsiUtil; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.BindingTrace; +import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; +import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResultsImpl; + +public class TailRecursionDetectorExtension extends RecursiveCallResolverExtension { + + public static final TailRecursionDetectorExtension INSTANCE = new TailRecursionDetectorExtension(); + + @Override + protected void runImpl( + @NotNull JetCallExpression callExpression, + @NotNull ResolvedCall resolvedCall, + @NotNull BasicCallResolutionContext context + ) { + BindingTrace trace = context.trace; + + TailRecursionKind recursionKind = + JetPsiUtil.visitUpwardToRoot(callExpression, new TailRecursionDetectorVisitor(), TailRecursionKind.MIGHT_BE); + + trace.record(BindingContext.TAIL_RECURSION_CALL, + callExpression, + recursionKind); + + switch (recursionKind) { + case NON_TAIL: + trace.report(Errors.NON_TAIL_RECURSIVE_CALL.on(callExpression)); + break; + case IN_TRY: + trace.report(Errors.TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED.on(callExpression)); + break; + default: + break; + } + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionDetectorVisitor.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionDetectorVisitor.java new file mode 100644 index 00000000000..8e2e6ba15db --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionDetectorVisitor.java @@ -0,0 +1,205 @@ +/* + * 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.resolve.calls; + +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lexer.JetTokens; + +public class TailRecursionDetectorVisitor extends JetVisitor, VisitorData> { + + @Override + public BacktraceVisitorStatus visitNamedFunction( + @NotNull JetNamedFunction function, VisitorData data + ) { + return new BacktraceVisitorStatus(data.data, true); + } + + @Override + public BacktraceVisitorStatus visitTryExpression( + @NotNull JetTryExpression expression, VisitorData data + ) { + return noTailRecursion(TailRecursionKind.IN_TRY); + } + + @Override + public BacktraceVisitorStatus visitCatchSection( + @NotNull JetCatchClause catchClause, VisitorData data + ) { + return continueTrace(data); + } + + @Override + public BacktraceVisitorStatus visitFinallySection( + @NotNull JetFinallySection finallySection, VisitorData data + ) { + return continueTrace(data); + } + + @Override + public BacktraceVisitorStatus visitBlockExpression( + @NotNull JetBlockExpression expression, VisitorData data + ) { + + if (data.data.isReturn()) return continueTrace(data); + + PsiElement child = data.last; + + JetElement last = findLastJetElement(expression.getLastChild()); + if (last != child) { // if last statement + if (!(last instanceof JetReturnExpression)) { + return noTailRecursion(); + } + + JetReturnExpression returnExpression = (JetReturnExpression) last; + if (returnExpression.getReturnedExpression() != null) { + return noTailRecursion(); + } + + if (findLastJetElement(returnExpression.getPrevSibling()) != child) { + return noTailRecursion(); + } + } + + return continueTrace(data); + } + + @Override + public BacktraceVisitorStatus visitWhenExpression( + @NotNull JetWhenExpression expression, VisitorData data + ) { + if (expression.getSubjectExpression() == data.last) { + return noTailRecursion(); + } + + return continueTrace(data); + } + + @Override + public BacktraceVisitorStatus visitWhenEntry( + @NotNull JetWhenEntry jetWhenEntry, VisitorData data + ) { + return continueTrace(data); + } + + @Override + public BacktraceVisitorStatus visitWhenConditionExpression( + @NotNull JetWhenConditionWithExpression condition, VisitorData data + ) { + return noTailRecursion(); + } + + @Override + public BacktraceVisitorStatus visitIfExpression( + @NotNull JetIfExpression expression, VisitorData data + ) { + if (expression.getCondition() == data.last) { + return noTailRecursion(); + } + + return continueTrace(data); + } + + @Override + public BacktraceVisitorStatus visitBinaryExpression( + @NotNull JetBinaryExpression expression, VisitorData data + ) { + if (expression.getOperationToken() == JetTokens.ELVIS) { + if (expression.getLeft() == data.last) { + return noTailRecursion(); + } + else if (expression.getRight() == data.last) { + return continueTrace(data); + } + else { + return noTailRecursion(); + } + } + + return super.visitBinaryExpression(expression, data); + } + + @Override + public BacktraceVisitorStatus visitQualifiedExpression( + @NotNull JetQualifiedExpression expression, VisitorData data + ) { + if (!(expression.getReceiverExpression() instanceof JetThisExpression)) { + return noTailRecursion(); + } + + return continueTrace(data); + } + + @Override + public BacktraceVisitorStatus visitReturnExpression( + @NotNull JetReturnExpression expression, VisitorData data + ) { + JetExpression returned = expression.getReturnedExpression(); + if (returned == null) { + throw new IllegalStateException("Bad case: how could we reach void return?"); + } + + return continueTrace(data, TailRecursionKind.IN_RETURN); + } + + @Override + public BacktraceVisitorStatus visitJetElement( + @NotNull JetElement element, VisitorData state + ) { + if (element instanceof JetContainerNode) { + return continueTrace(state); + } + + if (state.data == TailRecursionKind.MIGHT_BE) { + return noTailRecursion(); + } + + return continueTrace(state); + } + + private static BacktraceVisitorStatus noTailRecursion() { + return new BacktraceVisitorStatus(TailRecursionKind.NON_TAIL, true); + } + + private static BacktraceVisitorStatus noTailRecursion(TailRecursionKind status) { + return new BacktraceVisitorStatus(status, true); + } + + private static BacktraceVisitorStatus continueTrace(VisitorData data) { + return continueTrace(data, TailRecursionKind.MIGHT_BE); + } + + private static BacktraceVisitorStatus continueTrace(VisitorData data, TailRecursionKind desiredStatus) { + TailRecursionKind newStatus = data.data.and(desiredStatus); + return new BacktraceVisitorStatus(newStatus, !newStatus.isDoGenerateTailRecursion()); + } + + @Nullable + private static JetElement findLastJetElement(@Nullable PsiElement rightNode) { + PsiElement node = rightNode; + while (node != null) { + if (node instanceof JetElement) { + return (JetElement) node; + } + node = node.getPrevSibling(); + } + + return null; + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionKind.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionKind.java new file mode 100644 index 00000000000..13e6ff537a3 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/TailRecursionKind.java @@ -0,0 +1,61 @@ +/* + * 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.resolve.calls; + +public enum TailRecursionKind { + MIGHT_BE(true), + IN_RETURN(true), + IN_TRY(false), + NON_TAIL(false); + + private final boolean doGenerateTailRecursion; + + TailRecursionKind(boolean doGenerateTailRecursion) { + this.doGenerateTailRecursion = doGenerateTailRecursion; + } + + public boolean isDoGenerateTailRecursion() { + return doGenerateTailRecursion; + } + + public boolean isReturn() { + return this == IN_RETURN; + } + + public TailRecursionKind and(TailRecursionKind b) { + if (this == b) { + return this; + } + if (!this.isDoGenerateTailRecursion()) { + return this; + } + if (!b.isDoGenerateTailRecursion()) { + return this; + } + + if (isOneOf(this, b, IN_RETURN)) { + return IN_RETURN; + } + + return this; + } + + private static boolean isOneOf(TailRecursionKind a, TailRecursionKind b, TailRecursionKind value) { + return a == value || b == value; + } + +} diff --git a/compiler/testData/diagnostics/tests/tailRecursions/functionWithNoTails.kt b/compiler/testData/diagnostics/tests/tailRecursions/functionWithNoTails.kt new file mode 100644 index 00000000000..b9252e9ace5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/tailRecursions/functionWithNoTails.kt @@ -0,0 +1,3 @@ +tailRecursive fun noTails() { + // nothing here +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/tailRecursions/functionWithNonTailRecursions.kt b/compiler/testData/diagnostics/tests/tailRecursions/functionWithNonTailRecursions.kt new file mode 100644 index 00000000000..664feb3001a --- /dev/null +++ b/compiler/testData/diagnostics/tests/tailRecursions/functionWithNonTailRecursions.kt @@ -0,0 +1,12 @@ +tailRecursive fun badTails(x : Int) : Int { + if (x > 0) { + return 1 + badTails(x - 1) + } + else if (x == 10) { + [suppress("NON_TAIL_RECURSIVE_CALL")] + return 1 + badTails(x - 1) + } else if (x == 50) { + return badTails(x - 1) + } + return 0 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/tailRecursions/functionWithoutAnnotation.kt b/compiler/testData/diagnostics/tests/tailRecursions/functionWithoutAnnotation.kt new file mode 100644 index 00000000000..be3e538dc6d --- /dev/null +++ b/compiler/testData/diagnostics/tests/tailRecursions/functionWithoutAnnotation.kt @@ -0,0 +1,6 @@ +fun withoutAnnotation(x : Int) : Int { + if (x > 0) { + return 1 + withoutAnnotation(x - 1) + } + return 0 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/tailRecursions/tailRecursionInFinally.kt b/compiler/testData/diagnostics/tests/tailRecursions/tailRecursionInFinally.kt new file mode 100644 index 00000000000..cc3c94d4f68 --- /dev/null +++ b/compiler/testData/diagnostics/tests/tailRecursions/tailRecursionInFinally.kt @@ -0,0 +1,9 @@ +tailRecursive fun test() : Unit { + try { + test() + } catch (any : Exception) { + test() + } finally { + test() + } +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index c742a03e152..887c35601d8 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -33,7 +33,7 @@ import org.jetbrains.jet.checkers.AbstractDiagnosticsTestWithEagerResolve; @InnerTestClasses({JetDiagnosticsTestGenerated.Tests.class, JetDiagnosticsTestGenerated.Script.class}) public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEagerResolve { @TestMetadata("compiler/testData/diagnostics/tests") - @InnerTestClasses({Tests.Annotations.class, Tests.BackingField.class, Tests.CallableReference.class, Tests.Cast.class, Tests.CheckArguments.class, Tests.ClassObjects.class, Tests.ControlFlowAnalysis.class, Tests.ControlStructures.class, Tests.DataClasses.class, Tests.DataFlow.class, Tests.DataFlowInfoTraversal.class, Tests.DeclarationChecks.class, Tests.DelegatedProperty.class, Tests.Deparenthesize.class, Tests.Enum.class, Tests.Evaluate.class, Tests.Extensions.class, Tests.FunctionLiterals.class, Tests.Generics.class, Tests.IncompleteCode.class, Tests.Inference.class, Tests.Infos.class, Tests.Inline.class, Tests.Inner.class, Tests.J_k.class, Tests.Jdk_annotations.class, Tests.Library.class, Tests.NullabilityAndAutoCasts.class, Tests.NullableTypes.class, Tests.Numbers.class, Tests.Objects.class, Tests.OperatorsOverloading.class, Tests.Overload.class, Tests.Override.class, Tests.Recovery.class, Tests.Redeclarations.class, Tests.Regressions.class, Tests.Resolve.class, Tests.Scopes.class, Tests.SenselessComparison.class, Tests.Shadowing.class, Tests.SmartCasts.class, Tests.Substitutions.class, Tests.Subtyping.class, Tests.Suppress.class, Tests.ThisAndSuper.class, Tests.Varargs.class, Tests.When.class}) + @InnerTestClasses({Tests.Annotations.class, Tests.BackingField.class, Tests.CallableReference.class, Tests.Cast.class, Tests.CheckArguments.class, Tests.ClassObjects.class, Tests.ControlFlowAnalysis.class, Tests.ControlStructures.class, Tests.DataClasses.class, Tests.DataFlow.class, Tests.DataFlowInfoTraversal.class, Tests.DeclarationChecks.class, Tests.DelegatedProperty.class, Tests.Deparenthesize.class, Tests.Enum.class, Tests.Evaluate.class, Tests.Extensions.class, Tests.FunctionLiterals.class, Tests.Generics.class, Tests.IncompleteCode.class, Tests.Inference.class, Tests.Infos.class, Tests.Inline.class, Tests.Inner.class, Tests.J_k.class, Tests.Jdk_annotations.class, Tests.Library.class, Tests.NullabilityAndAutoCasts.class, Tests.NullableTypes.class, Tests.Numbers.class, Tests.Objects.class, Tests.OperatorsOverloading.class, Tests.Overload.class, Tests.Override.class, Tests.Recovery.class, Tests.Redeclarations.class, Tests.Regressions.class, Tests.Resolve.class, Tests.Scopes.class, Tests.SenselessComparison.class, Tests.Shadowing.class, Tests.SmartCasts.class, Tests.Substitutions.class, Tests.Subtyping.class, Tests.Suppress.class, Tests.TailRecursions.class, Tests.ThisAndSuper.class, Tests.Varargs.class, Tests.When.class}) public static class Tests extends AbstractDiagnosticsTestWithEagerResolve { @TestMetadata("Abstract.kt") public void testAbstract() throws Exception { @@ -6456,6 +6456,34 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage } } + @TestMetadata("compiler/testData/diagnostics/tests/tailRecursions") + public static class TailRecursions extends AbstractDiagnosticsTestWithEagerResolve { + public void testAllFilesPresentInTailRecursions() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/diagnostics/tests/tailRecursions"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("functionWithNoTails.kt") + public void testFunctionWithNoTails() throws Exception { + doTest("compiler/testData/diagnostics/tests/tailRecursions/functionWithNoTails.kt"); + } + + @TestMetadata("functionWithNonTailRecursions.kt") + public void testFunctionWithNonTailRecursions() throws Exception { + doTest("compiler/testData/diagnostics/tests/tailRecursions/functionWithNonTailRecursions.kt"); + } + + @TestMetadata("functionWithoutAnnotation.kt") + public void testFunctionWithoutAnnotation() throws Exception { + doTest("compiler/testData/diagnostics/tests/tailRecursions/functionWithoutAnnotation.kt"); + } + + @TestMetadata("tailRecursionInFinally.kt") + public void testTailRecursionInFinally() throws Exception { + doTest("compiler/testData/diagnostics/tests/tailRecursions/tailRecursionInFinally.kt"); + } + + } + @TestMetadata("compiler/testData/diagnostics/tests/thisAndSuper") public static class ThisAndSuper extends AbstractDiagnosticsTestWithEagerResolve { public void testAllFilesPresentInThisAndSuper() throws Exception { @@ -6688,6 +6716,7 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage suite.addTestSuite(Substitutions.class); suite.addTestSuite(Subtyping.class); suite.addTest(Suppress.innerSuite()); + suite.addTestSuite(TailRecursions.class); suite.addTestSuite(ThisAndSuper.class); suite.addTestSuite(Varargs.class); suite.addTestSuite(When.class);