Tail-call detection support in the front-end
This commit is contained in:
committed by
Andrey Breslav
parent
83b4ad4eac
commit
1c81102941
@@ -280,6 +280,8 @@ public interface Errors {
|
||||
|
||||
DiagnosticFactory0<JetParameter> VALUE_PARAMETER_WITH_NO_TYPE_ANNOTATION = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
DiagnosticFactory0<JetNamedFunction> NO_TAIL_CALLS_FOUND = DiagnosticFactory0.create(WARNING, NAMED_ELEMENT);
|
||||
|
||||
// Named parameters
|
||||
|
||||
DiagnosticFactory0<JetParameter> DEFAULT_VALUE_NOT_ALLOWED_IN_OVERRIDE = DiagnosticFactory0.create(ERROR, PARAMETER_DEFAULT_VALUE);
|
||||
@@ -334,6 +336,9 @@ public interface Errors {
|
||||
DiagnosticFactory2<JetReferenceExpression, JetExpression, JetType> FUNCTION_EXPECTED = DiagnosticFactory2.create(ERROR);
|
||||
DiagnosticFactory2<JetExpression, JetExpression, Boolean> FUNCTION_CALL_EXPECTED = DiagnosticFactory2.create(ERROR, CALL_EXPRESSION);
|
||||
|
||||
DiagnosticFactory0<JetCallExpression> NON_TAIL_RECURSIVE_CALL = DiagnosticFactory0.create(WARNING, CALL_EXPRESSION);
|
||||
DiagnosticFactory0<JetCallExpression> TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED = DiagnosticFactory0.create(WARNING, CALL_EXPRESSION);
|
||||
|
||||
DiagnosticFactory0<PsiElement> NO_CONSTRUCTOR = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<JetExpression> NOT_A_CLASS = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
+3
-1
@@ -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,
|
||||
|
||||
@@ -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<T> {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -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> T visitUpwardToRoot(
|
||||
@NotNull PsiElement element,
|
||||
@NotNull JetVisitor<BacktraceVisitorStatus<T>, VisitorData<T>> visitor,
|
||||
T def
|
||||
) {
|
||||
List<PsiElement> track = new ArrayList<PsiElement>();
|
||||
List<PsiElement> view = Collections.unmodifiableList(track);
|
||||
@NotNull
|
||||
BacktraceVisitorStatus<T> lastStatus = new BacktraceVisitorStatus<T>(def, true);
|
||||
VisitorData<T> data = new VisitorData<T>(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<T> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> {
|
||||
public T data;
|
||||
@NotNull
|
||||
public final List<PsiElement> visitedPath;
|
||||
public PsiElement last;
|
||||
|
||||
public VisitorData(@NotNull List<PsiElement> visitedPath) {
|
||||
this.visitedPath = visitedPath;
|
||||
}
|
||||
}
|
||||
@@ -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<JetReferenceExpression, DeclarationDescriptor>(DO_NOTHING);
|
||||
WritableSlice<JetElement, ResolvedCall<? extends CallableDescriptor>> RESOLVED_CALL =
|
||||
new BasicWritableSlice<JetElement, ResolvedCall<? extends CallableDescriptor>>(DO_NOTHING);
|
||||
WritableSlice<JetCallExpression, TailRecursionKind> TAIL_RECURSION_CALL =
|
||||
new BasicWritableSlice<JetCallExpression, TailRecursionKind>(DO_NOTHING, true);
|
||||
WritableSlice<DeclarationDescriptor, List<JetCallExpression>> FUNCTION_RECURSIVE_CALL_EXPRESSIONS =
|
||||
new BasicWritableSlice<DeclarationDescriptor, List<JetCallExpression>>(DO_NOTHING, false);
|
||||
WritableSlice<JetElement, ConstraintSystemCompleter> CONSTRAINT_SYSTEM_COMPLETER = new BasicWritableSlice<JetElement, ConstraintSystemCompleter>(DO_NOTHING);
|
||||
WritableSlice<JetElement, Call> CALL = new BasicWritableSlice<JetElement, Call>(DO_NOTHING);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+57
@@ -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<JetCallExpression> callExpressions = firstNonNull(
|
||||
trace.get(BindingContext.FUNCTION_RECURSIVE_CALL_EXPRESSIONS, descriptor),
|
||||
Collections.<JetCallExpression>emptyList());
|
||||
|
||||
boolean allNonTailRecursiveCalls = Iterables.all(callExpressions, new Predicate<JetCallExpression>() {
|
||||
@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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+53
@@ -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 <F extends CallableDescriptor> void runImpl(
|
||||
@NotNull JetCallExpression callExpression,
|
||||
@NotNull ResolvedCall<F> resolvedCall,
|
||||
@NotNull BasicCallResolutionContext context
|
||||
) {
|
||||
DeclarationDescriptor declarationDescriptor = context.scope.getContainingDeclaration();
|
||||
|
||||
List<JetCallExpression> expressions =
|
||||
context.trace.get(BindingContext.FUNCTION_RECURSIVE_CALL_EXPRESSIONS, declarationDescriptor);
|
||||
|
||||
if (expressions == null) {
|
||||
expressions = new ArrayList<JetCallExpression>(2);
|
||||
context.trace.record(BindingContext.FUNCTION_RECURSIVE_CALL_EXPRESSIONS, declarationDescriptor, expressions);
|
||||
}
|
||||
|
||||
expressions.add(callExpression);
|
||||
}
|
||||
}
|
||||
+45
@@ -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 <F extends CallableDescriptor> void run(
|
||||
@NotNull ResolvedCall<F> resolvedCall, @NotNull BasicCallResolutionContext context
|
||||
) {
|
||||
if (isRecursion(resolvedCall, context) && context.call.getCallElement() instanceof JetCallExpression) {
|
||||
runImpl((JetCallExpression) context.call.getCallElement(), resolvedCall, context);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract <F extends CallableDescriptor> void runImpl(@NotNull JetCallExpression callExpression,
|
||||
@NotNull ResolvedCall<F> resolvedCall, @NotNull BasicCallResolutionContext context);
|
||||
|
||||
private static <F extends CallableDescriptor> boolean isRecursion(
|
||||
@NotNull ResolvedCall<F> resolvedCall,
|
||||
@NotNull BasicCallResolutionContext context
|
||||
) {
|
||||
return resolvedCall.getResultingDescriptor().getOriginal().equals(context.scope.getContainingDeclaration());
|
||||
}
|
||||
}
|
||||
+60
@@ -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 <F extends CallableDescriptor> void runImpl(
|
||||
@NotNull JetCallExpression callExpression,
|
||||
@NotNull ResolvedCall<F> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+205
@@ -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<BacktraceVisitorStatus<TailRecursionKind>, VisitorData<TailRecursionKind>> {
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitNamedFunction(
|
||||
@NotNull JetNamedFunction function, VisitorData<TailRecursionKind> data
|
||||
) {
|
||||
return new BacktraceVisitorStatus<TailRecursionKind>(data.data, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitTryExpression(
|
||||
@NotNull JetTryExpression expression, VisitorData<TailRecursionKind> data
|
||||
) {
|
||||
return noTailRecursion(TailRecursionKind.IN_TRY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitCatchSection(
|
||||
@NotNull JetCatchClause catchClause, VisitorData<TailRecursionKind> data
|
||||
) {
|
||||
return continueTrace(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitFinallySection(
|
||||
@NotNull JetFinallySection finallySection, VisitorData<TailRecursionKind> data
|
||||
) {
|
||||
return continueTrace(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitBlockExpression(
|
||||
@NotNull JetBlockExpression expression, VisitorData<TailRecursionKind> 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<TailRecursionKind> visitWhenExpression(
|
||||
@NotNull JetWhenExpression expression, VisitorData<TailRecursionKind> data
|
||||
) {
|
||||
if (expression.getSubjectExpression() == data.last) {
|
||||
return noTailRecursion();
|
||||
}
|
||||
|
||||
return continueTrace(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitWhenEntry(
|
||||
@NotNull JetWhenEntry jetWhenEntry, VisitorData<TailRecursionKind> data
|
||||
) {
|
||||
return continueTrace(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitWhenConditionExpression(
|
||||
@NotNull JetWhenConditionWithExpression condition, VisitorData<TailRecursionKind> data
|
||||
) {
|
||||
return noTailRecursion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitIfExpression(
|
||||
@NotNull JetIfExpression expression, VisitorData<TailRecursionKind> data
|
||||
) {
|
||||
if (expression.getCondition() == data.last) {
|
||||
return noTailRecursion();
|
||||
}
|
||||
|
||||
return continueTrace(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitBinaryExpression(
|
||||
@NotNull JetBinaryExpression expression, VisitorData<TailRecursionKind> 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<TailRecursionKind> visitQualifiedExpression(
|
||||
@NotNull JetQualifiedExpression expression, VisitorData<TailRecursionKind> data
|
||||
) {
|
||||
if (!(expression.getReceiverExpression() instanceof JetThisExpression)) {
|
||||
return noTailRecursion();
|
||||
}
|
||||
|
||||
return continueTrace(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BacktraceVisitorStatus<TailRecursionKind> visitReturnExpression(
|
||||
@NotNull JetReturnExpression expression, VisitorData<TailRecursionKind> 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<TailRecursionKind> visitJetElement(
|
||||
@NotNull JetElement element, VisitorData<TailRecursionKind> state
|
||||
) {
|
||||
if (element instanceof JetContainerNode) {
|
||||
return continueTrace(state);
|
||||
}
|
||||
|
||||
if (state.data == TailRecursionKind.MIGHT_BE) {
|
||||
return noTailRecursion();
|
||||
}
|
||||
|
||||
return continueTrace(state);
|
||||
}
|
||||
|
||||
private static BacktraceVisitorStatus<TailRecursionKind> noTailRecursion() {
|
||||
return new BacktraceVisitorStatus<TailRecursionKind>(TailRecursionKind.NON_TAIL, true);
|
||||
}
|
||||
|
||||
private static BacktraceVisitorStatus<TailRecursionKind> noTailRecursion(TailRecursionKind status) {
|
||||
return new BacktraceVisitorStatus<TailRecursionKind>(status, true);
|
||||
}
|
||||
|
||||
private static BacktraceVisitorStatus<TailRecursionKind> continueTrace(VisitorData<TailRecursionKind> data) {
|
||||
return continueTrace(data, TailRecursionKind.MIGHT_BE);
|
||||
}
|
||||
|
||||
private static BacktraceVisitorStatus<TailRecursionKind> continueTrace(VisitorData<TailRecursionKind> data, TailRecursionKind desiredStatus) {
|
||||
TailRecursionKind newStatus = data.data.and(desiredStatus);
|
||||
return new BacktraceVisitorStatus<TailRecursionKind>(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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<!NO_TAIL_CALLS_FOUND!>tailRecursive fun noTails()<!> {
|
||||
// nothing here
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
tailRecursive fun badTails(x : Int) : Int {
|
||||
if (x > 0) {
|
||||
return 1 + <!NON_TAIL_RECURSIVE_CALL!>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
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun withoutAnnotation(x : Int) : Int {
|
||||
if (x > 0) {
|
||||
return 1 + withoutAnnotation(x - 1)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<!NO_TAIL_CALLS_FOUND!>tailRecursive fun test() : Unit<!> {
|
||||
try {
|
||||
<!TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED!>test<!>()
|
||||
} catch (any : Exception) {
|
||||
<!TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED!>test<!>()
|
||||
} finally {
|
||||
<!TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED!>test<!>()
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user